这是一个简化的例子,用于隔离问题的目的。在我的实际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?
我也欢迎其他建议,但希望保留基本的闭包结构(即,符合问题的范围)。
答案 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()...");
}
}