3

这是一个简化的示例,用于隔离问题的目的。在我的实际场景中,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());
    }


}

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

有没有办法在返回的 lambda 函数中添加“析构函数”,一旦 lambda 函数超出范围(不再引用),我就可以在其中处理 TextReader?

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

4

6 回答 6

3

如果您不需要 lambda 表达式,则可以创建 enumerable 。

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

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处理实现 IDisposable 的迭代器来处理此类情况。

于 2012-07-20T00:16:10.090 回答
2

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

于 2012-07-20T00:17:21.757 回答
0

我真的很喜欢产量解决方案。我编写了一个简单的代码,它表明它运行良好,可以在客户端退出 for-each 之后处理资源。

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()...");
    }
 }
于 2012-07-20T02:10:36.947 回答
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);
    }

}
于 2012-07-20T00:19:38.887 回答
0

如果你真的想保留闭包语义,你需要为它添加一个参数。类似于下面的东西,但您必须注意调用 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();
    }
}
于 2012-07-20T00:28:28.097 回答
0

这比简单地返回 TextReader 更容易吗?在我看来,您只是为了实现特定的编码风格而使事情变得更加复杂。

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

于 2012-07-20T01:23:09.880 回答