C# 在闭包中捕获TextReader但仍正确处理它的好方法是什么?

C# 在闭包中捕获TextReader但仍正确处理它的好方法是什么?,c#,.net,lambda,closures,C#,.net,Lambda,Closures,这是一个简化的示例,用于分离问题的目的。在我实际的scneario中,GetColumnReader返回的ColumnReader实际上要做的工作不仅仅是ReadLine 如果我运行下面的程序,当我试图调用Reader()时,我会得到一个错误,因为当然,文本阅读器已经被using语句处理掉了 public class Play{ delegate string ColumnReader(); static ColumnReader GetColumnReader(string f

这是一个简化的示例,用于分离问题的目的。在我实际的scneario中,GetColumnReader返回的ColumnReader实际上要做的工作不仅仅是ReadLine

如果我运行下面的程序,当我试图调用Reader()时,我会得到一个错误,因为当然,文本阅读器已经被using语句处理掉了

public class Play{
    delegate string ColumnReader();
    static ColumnReader GetColumnReader(string filename){
        using (TextReader reader = new StreamReader(filename)){
            var headers = reader.ReadLine();
            return () => reader.ReadLine();
        }
    }
    public static void Main(string[] args){
        var Reader = GetColumnReader("Input.tsv");
        Console.WriteLine(Reader());
    }


}
或者,我可以删除“using”并直接声明TextReader,这将起作用,但现在我们不再保证TextReader最终会关闭

有没有办法在返回的lambda函数中添加一个“析构函数”,以便在lambda函数超出范围(没有更多引用)时,我可以立即处理TextReader


我也欢迎其他建议,但希望保留基本的闭包结构(即,符合问题的范围)。

如果不需要lambda表达式,可以创建可枚举的

在=>{}中使用移动可能在实际代码中起作用。。。仍然可能不是您想要的:

static ColumnReader GetColumnReader(string filename) {
   return () => {
     using (TextReader reader = new StreamReader(filename)) {
        var headers = reader.ReadLine();
        return reader.ReadLine();
     }
  };
}
带有IEnumerable的版本(如果始终完成迭代):

静态IEnumerable GetColumnReader(字符串文件名){
使用(TextReader=new StreamReader(“aa”)){
var headers=reader.ReadLine();
产生返回reader.ReadLine();
}
}

如果希望支持枚举中间的迭代,则需要创建自定义IDisposable迭代器。查看如何处理此类情况。

本质上,您需要委托本身之外的一次性元素的作用域。在这些情况下,我会让代理接受一次性实例(即TextReader),而不是文件名

public class Play {

    delegate string ColumnReader();

    static ColumnReader GetColumnReader(string filename) {
        return () => {
            using (TextReader reader = new StreamReader(filename)) {
                var headers = reader.ReadLine();
                return reader.ReadLine();
            }
        };
    }

    public static void Main(string[] args) {
        var Reader = GetColumnReader("Input.tsv");
        Console.WriteLine(Reader());
    }


}
显然,每次调用返回的委托时,这将打开/读取一行/关闭文件

如果需要打开它一次,然后在阅读几行时保持打开状态,最好使用迭代器块,如下所示:

public class Play {

    static IEnumerable<string> ReadLines(string filename) {
        using (TextReader reader = new StreamReader(filename)) {
            var headers = reader.ReadLine(); // I'm guessing you want to ignore this??
            while (true) {
                string line = reader.ReadLine();
                if (line == null)
                    yield break;
                yield return line;
            }
        }
    }

    public static void Main(string[] args) {
        foreach (string line in ReadLines("Input.tsv"))
            Console.WriteLine(line);
    }

}
公共类游戏{
静态IEnumerable可读行(字符串文件名){
使用(TextReader=new StreamReader(文件名)){
var headers=reader.ReadLine();//我猜您想忽略这个??
while(true){
字符串行=reader.ReadLine();
如果(行==null)
屈服断裂;
收益率回归线;
}
}
}
公共静态void Main(字符串[]args){
foreach(ReadLines中的字符串行(“Input.tsv”))
控制台写入线(行);
}
}

如果您确实想保留闭包语义,则需要为其添加一个参数。类似于bellow,但您必须注意调用dispose命令

public class Play {
    enum ReaderCommand {
        Read,
        Close
    }

    delegate string ColumnReader(ReaderCommand cmd);

    static ColumnReader GetColumnReader(string filename) {
        TextReader reader = new StreamReader(filename);
        var headers = reader.ReadLine();
        return (ReaderCommand cmd) => {
            switch (cmd) {
                case ReaderCommand.Read:
                    return reader.ReadLine();

                case ReaderCommand.Close:
                    reader.Dispose();
                    return null;
            }

            return null;
        };
    }

    public static void Main(string[] args) {
        var Reader = GetColumnReader("Input.tsv");
        Console.WriteLine(Reader(ReaderCommand.Read));
        Console.WriteLine(Reader(ReaderCommand.Read));
        Reader(ReaderCommand.Close);
        Console.ReadKey();
    }
}

这比简单地返回文本阅读器更容易吗?在我看来,你只是为了达到一种特定的编码风格而使事情变得更加复杂

调用方始终有责任正确处理返回的任何内容。
我相信你的项目会给你足够的机会来锻炼你的肌肉——这次只是简单一点

我非常喜欢收益率解决方案。我已经编写了一个简单的代码,它表明它工作得很好,资源可以被释放,在客户端为每个

static void Main(string[] args)
{
    using (Resource resource = new Resource())
    {
       foreach (var number in resource.GetNumbers())
       {
          if (number > 2)
             break;
          Console.WriteLine(number);
       }
     }
     Console.Read();
 }
 public class Resource : IDisposable
 {
    private List<int> _numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7 };

    public IEnumerable<int> GetNumbers()
    {
       foreach (var number in _numbers)
          yield return number;
    }

    public void Dispose()
    {
       Console.WriteLine("Resource::Dispose()...");
    }
 }
static void Main(字符串[]args)
{
使用(资源=新资源())
{
foreach(resource.GetNumbers()中的变量编号)
{
如果(数量>2)
打破
控制台写入线(编号);
}
}
Console.Read();
}
公共类资源:IDisposable
{
私人名单编号=新名单{1,2,3,4,5,6,7};
公共IEnumerable GetNumbers()
{
foreach(变量编号在_编号中)
收益返回数;
}
公共空间处置()
{
WriteLine(“Resource::Dispose()…”;
}
}

1)使用{}是一种很好的处理方法2)我看不到Reader()的实现@HatSoft,2?“var Reader=GetColumnReader”。。。局部变量名的大小写显然不寻常,但它仍然有效……您需要调用Reader()闭包一次还是多次?@Smilediver,我可能需要调用多次,但在文件完成之前不一定。请改用迭代器。我喜欢这个解决方案,因为它直观地表明,一次性文件的范围由最终使用它的方法控制……也就是说,调用闭包的方法。
static void Main(string[] args)
{
    using (Resource resource = new Resource())
    {
       foreach (var number in resource.GetNumbers())
       {
          if (number > 2)
             break;
          Console.WriteLine(number);
       }
     }
     Console.Read();
 }
 public class Resource : IDisposable
 {
    private List<int> _numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7 };

    public IEnumerable<int> GetNumbers()
    {
       foreach (var number in _numbers)
          yield return number;
    }

    public void Dispose()
    {
       Console.WriteLine("Resource::Dispose()...");
    }
 }