奥莱特处置的争论,这里有云的好一段坏的代码:的迭代器块
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>
字段中,以便在继续进行实际解析之前进一步进行迭代(而不是一次又一次地解析;或者不得不解析一个巨大的日志ju st来查询它的一小部分)。
请注意,这种优化实际上是需要,它的大部分已经工作(如果我删除了using
语句,一切都很好,除了泄漏的文件句柄)。
Parse
和Tokenize
都是迭代器块(AFAIK,唯一可以同时推迟执行和清理代码的方法);他们的签名是IEnumerable<Event> Parse(IEnumerable<Token>)
和IEnumerable<Token> Tokenize(StreamReader)
。他们的逻辑与这个问题无关。
逻辑流程非常清晰;代码的每个部分的意图都很明显;但那些using
块与整个延迟执行的事情没有相处(当我通过我的Log
对象枚举时,using
已经退出并且流被丢弃,所以Tokenize
试图从其中读取悲惨崩溃)。
我可以在文件(开放流)上锁定相对较长的时间,但迟早我不得不关闭它。由于我无法真正使用using
,我必须明确处理这些流。
问题是:我应该在哪里拨打电话Dispose()
?有没有什么常见的成语来处理这些情景?我不希望这样做是“旧的方式”(在几个地方发布资源,每次发布时都要仔细检查每个发布版本,等等)。
我的第一个想法是使Log
类一次性的,所以它的构造可以采取一个文件名,并有类内的所有资源管理(只要求消费者在完成时处置Log
本身的),但我在调用base
构造函数之前无法创建并保存流(该流是产生该构造函数参数的调用所必需的)。
注意:除非严格需要,否则不应触及CachingProxyList
(我想保持足够的通用性以使其可重用)。特别是,构造函数对于强制执行某些不变量是必不可少的,其余的实现严重依赖于其中(例如内部枚举器对象永远不为null)。其他一切,OTOH,应该是公平的游戏。
感谢您的耐心,如果您已阅读本文,并且也提前感谢您提供的任何帮助;)
。
这是微妙的,但导致了一个解决方案:它将问题转化为决定谁应该拥有这些流,并且我猜'Tokenize'方法是最好的候选者(毕竟它是唯一真正使用它的人)。 “所有者负责在资源上调用Dispose()”明确回答了问题,并且“每个资源都必须有一个所有者”,这使问题的根源显而易见。谢谢! – 2011-04-05 23:56:51
关于编辑的一个注意事项:正如我在问题中提到的那样,使Log类是一次性是我的第一个想法,但有一些问题(将流传递给'Tokenize',但也保存以备后用)。实际的解决方案是将文件名本身一直传递给'Tokenize',然后该方法将创建“拥有”,并处理这些流。在一个侧面说明中,我认为突出“一个所有者”的观点是一个好主意:解决这个问题一直是关键。 – 2011-04-06 00:13:12
+1。 herenvardo,如果流(或任何其他类似的资源)由其他东西所拥有,则可以不在开放流附近“使用”。将文件名传递给您的Log类使其负责同时处理两件事 - 打开文件和读取日志,考虑实际将流传递给拆分责任。至少考虑有两种风味(检查可能的方法XmlWriter.Create方法)。 – 2011-04-06 01:22:31