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

时间:2012-07-20 00:01:57

标签: c# .net lambda closures

这是一个简化的例子,用于隔离问题的目的。在我的实际scneario中,GetColumnReader返回的ColumnReader实际上会比ReadLine做更多的工作。

如果我运行以下程序,当我尝试调用Reader()时会出错,因为TextReader当然已经被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?

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

6 个答案:

答案 0 :(得分:3)

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

使用=> {}内部潜在移动可能会在实际代码中运行...仍然可能不是您正在寻找的内容:

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

具有IEnumerable的版本(如果你总是完成迭代):

 static IEnumerable<string> GetColumnReader(string filename) {
   using (TextReader reader = new StreamReader("aa")) {
     var headers = reader.ReadLine();
     yield return reader.ReadLine();
   }
 }

如果要支持迭代到枚举的中间,则需要创建自定义IDisposable迭代器。了解foreach handles iterators that implement IDisposable如何处理此类案件。

答案 1 :(得分:2)

基本上,你需要代表本身之外的一次性元素的范围。在这些情况下,我会让委托接受一次性实例(即文本读取器)而不是文件名。

答案 2 :(得分:0)

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);
    }

}

答案 3 :(得分:0)

如果你真的想保留闭包语义,你需要为它添加一个参数。像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();
    }
}

答案 4 :(得分:0)

这比简单地返回TextReader更容易吗?在我看来,为了实现特定的编码风格,你的工作要复杂得多。

责任总是在调用者身上处理正确返回的内容。 我相信你的项目会给你很多机会来锻炼你的肌肉 - 这次只是保持简单!

答案 5 :(得分:0)

我非常喜欢屈服解决方案。我写了一个简单的代码,它表明它运行良好,资源可以在客户端出来之后被处理掉。

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()...");
    }
 }