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

现在对这个问题有一些背景:

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

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

逻辑流程非常清晰;并且代码各部分的意图相当明显;但是这些using块并不能与整个延迟执行的事情相处(当我枚举我的Log对象时,using已经退出并且流已被处理,所以Tokenize从它读取的尝试惨遭崩溃)。

我可以承受相对较长时间锁定文件(打开的流),但迟早我必须关闭它。因为我不能真正使用usings,所以我必须明确地处理流。

问题是:我应该把电话放在哪里Dispose()?有没有常见的习惯用法来处理这样的场景?我不希望以“旧方式”(在多个地方发布资源,必须随时审查每个版本的代码在某处发生一点点更改,等等)来执行此操作。

我的第一个想法是使Log类一次性,因此它的构造函数可以采用文件名并在类中进行所有资源管理(只需要消费者Log在完成后自行处理),但我看不到创建的方法并在调用base构造函数之前保存流(为该构造函数产生参数的调用需要流)。

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

感谢您阅读本文后的耐心等待,并提前感谢您提供的任何帮助;)

4

1 回答 1

5
  • 封装非托管资源的类需要实现dispose模式(IDisposable接口)。例如流、数据库连接等
  • 每个资源必须有一个所有者
  • 所有者负责调用Dispose()资源
  • 如果所有者不能立即调用Dispose()它的资源或者不知道何时调用它,那么它需要自己实现IDisposable接口并在其中调用Dispose()它的资源。

上述陈述可能有例外,但这是一般规则。示例是StreamWriter接收流(实现IDisposable接口)并强制它自己实现IDisposable接口 -因为它不知道何时处置它

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

如您所见,这变成了一个链,而非一次性客户端将不得不对其使用的资源调用 dispose 并且该资源将释放其资源等...

于 2011-04-05T23:35:52.257 回答