好吧,这是一段很好的坏代码:
public class Log : CachingProxyList<Event> {
public static Log FromFile(String fullPath) {
using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read)) {
using (StreamReader sr = new StreamReader(fs)) {
return new Log(sr);
}
}
}
public Log(StreamReader stream)
: base(Parser.Parse(Parser.Tokenize(stream))) {
/* Here goes some "magic", the whole reason for this
* class to exist, but not really relevant to the issue */
}
}
现在对这个问题有一些背景:
CachingProxyList
是一个IEnumerable<T>
提供自定义“缓存”枚举器的实现:它IEnumerable<T>
在其构造函数上接受一个,并最初通过它进行枚举,但将每个项目保存在一个私有List<T>
字段上,以便在继续实际解析之前进一步迭代运行(而不是时不时地解析;或者不得不解析一个巨大的日志来查询它的一小部分)。
请注意,实际上需要这种优化,并且大部分已经在工作(如果我删除using
语句,除了泄漏的文件句柄之外一切都很好)。
Parse
和都是Tokenize
迭代器块(AFAIK,我可以同时延迟执行和清理代码的唯一明智的方法);他们的签名是IEnumerable<Event> Parse(IEnumerable<Token>)
和IEnumerable<Token> Tokenize(StreamReader)
。他们的逻辑与问题无关。
逻辑流程非常清晰;并且代码各部分的意图相当明显;但是这些using
块并不能与整个延迟执行的事情相处(当我枚举我的Log
对象时,using
已经退出并且流已被处理,所以Tokenize
从它读取的尝试惨遭崩溃)。
我可以承受相对较长时间锁定文件(打开的流),但迟早我必须关闭它。因为我不能真正使用using
s,所以我必须明确地处理流。
问题是:我应该把电话放在哪里Dispose()
?有没有常见的习惯用法来处理这样的场景?我不希望以“旧方式”(在多个地方发布资源,必须随时审查每个版本的代码在某处发生一点点更改,等等)来执行此操作。
我的第一个想法是使Log
类一次性,因此它的构造函数可以采用文件名并在类中进行所有资源管理(只需要消费者Log
在完成后自行处理),但我看不到创建的方法并在调用base
构造函数之前保存流(为该构造函数产生参数的调用需要流)。
注意:CachingProxyList
除非严格需要,否则不应触摸(我想保持它足够通用以使其可重用)。特别是,构造函数对于强制执行其余部分严重依赖的一些不变量是必不可少的(例如内部枚举器对象永远不会为空)。其他一切,OTOH,应该是公平的游戏。
感谢您阅读本文后的耐心等待,并提前感谢您提供的任何帮助;)
。