2011-04-05 79 views
3

奥莱特处置的争论,这里有云的好一段坏的代码:的迭代器块

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 */ 
    } 
} 

现在有些情况下进入问题:

CachingProxyListIEnumerable<T>的实现,提供了一个自定义的“缓存”枚举器:它的构造函数需要IEnumerable<T>,并且最初通过它进行枚举,但将每个项目保存在私有的List<T>字段中,以便在继续进行实际解析之前进一步进行迭代(而不是一次又一次地解析;或者不得不解析一个巨大的日志ju st来查询它的一小部分)。
请注意,这种优化实际上是需要,它的大部分已经工作(如果我删除了using语句,一切都很好,除了泄漏的文件句柄)。

ParseTokenize都是迭代器块(AFAIK,唯一可以同时推迟执行和清理代码的方法);他们的签名是IEnumerable<Event> Parse(IEnumerable<Token>)IEnumerable<Token> Tokenize(StreamReader)。他们的逻辑与这个问题无关。

逻辑流程非常清晰;代码的每个部分的意图都很明显;但那些using块与整个延迟执行的事情没有相处(当我通过我的Log对象枚举时,using已经退出并且流被丢弃,所以Tokenize试图从其中读取悲惨崩溃)。

我可以在文件(开放流)上锁定相对较长的时间,但迟早我不得不关闭它。由于我无法真正使用using,我必须明确处理这些流。

问题是:我应该在哪里拨打电话Dispose()?有没有什么常见的成语来处理这些情景?我不希望这样做是“旧的方式”(在几个地方发布资源,每次发布时都要仔细检查每个发布版本,等等)。

我的第一个想法是使Log类一次性的,所以它的构造可以采取一个文件名,并有类内的所有资源管理(只要求消费者在完成时处置Log本身的),但我在调用base构造函数之前无法创建并保存流(该流是产生该构造函数参数的调用所必需的)。

注意:除非严格需要,否则不应触及CachingProxyList(我想保持足够的通用性以使其可重用)。特别是,构造函数对于强制执行某些不变量是必不可少的,其余的实现严重依赖于其中(例如内部枚举器对象永远不为null)。其他一切,OTOH,应该是公平的游戏。

感谢您的耐心,如果您已阅读本文,并且也提前感谢您提供的任何帮助;)

回答

5
  • 封装非托管资源的类需要实现配置模式(IDisposable接口)。例如流,数据库连接等
  • 每个资源都必须有一个所有者
  • 业主负责对资源
  • 如果业主不能立即致电其资源Dispose()或者不知道什么时候打电话叫Dispose()那么它需要实现IDisposable接口本身,并在其中的资源上调用Dispose()

上述声明可能有例外,但这是一般规则。例如StreamWriter需要一个流(实现IDisposable接口),并强制它实现IDisposable接口本身 - ,因为它不知道何时处置它

你的情况是一样的。你的班级使用一次性资源,但不知道何时处理它 - 或者这就是我所假设的。这将使它实现IDisposable接口。您的Log班级的客户必须在班上拨打Dispose()

所以你可以看到,这成为一个链,而非一次性的客户将不得不调用Dispose的资源,它使用和资源将配置其资源,等等

+0

这是微妙的,但导致了一个解决方案:它将问题转化为决定谁应该拥有这些流,并且我猜'Tokenize'方法是最好的候选者(毕竟它是唯一真正使用它的人)。 “所有者负责在资源上调用Dispose()”明确回答了问题,并且“每个资源都必须有一个所有者”,这使问题的根源显而易见。谢谢! – 2011-04-05 23:56:51

+0

关于编辑的一个注意事项:正如我在问题中提到的那样,使Log类是一次性是我的第一个想法,但有一些问题(将流传递给'Tokenize',但也保存以备后用)。实际的解决方案是将文件名本身一直传递给'Tokenize',然后该方法将创建“拥有”,并处理这些流。在一个侧面说明中,我认为突出“一个所有者”的观点是一个好主意:解决这个问题一直是关键。 – 2011-04-06 00:13:12

+0

+1。 herenvardo,如果流(或任何其他类似的资源)由其他东西所拥有,则可以不在开放流附近“使用”。将文件名传递给您的Log类使其负责同时处理两件事 - 打开文件和读取日志,考虑实际将流传递给拆分责任。至少考虑有两种风味(检查可能的方法XmlWriter.Create方法)。 – 2011-04-06 01:22:31