处理迭代器块参数

3
好的,下面是一段糟糕的代码:
```html

好吧,接下来是一段糟糕的代码:

```
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的实现,它提供了一个自定义的“缓存”枚举器:它在构造函数中接收一个IEnumerable,最初枚举它,但将每个项保存在一个私有的List字段中,因此进一步迭代会先通过该列表运行而不是每次都解析(或者必须解析大量日志才能查询其中的一小部分)。 请注意,这种优化实际上是必需的,大部分已经工作正常(如果删除using语句,则除泄漏文件句柄外一切正常)。
Parse和Tokenize都是迭代块(据我所知,这是同时延迟执行和清晰代码的唯一明智方法);它们的签名是IEnumerable Parse(IEnumerable)和IEnumerable Tokenize(StreamReader)。它们的逻辑与问题无关。
逻辑流程非常清晰,每部分代码的意图也很明显,但这些using块与整个延迟执行机制不兼容(当我枚举Log对象时,using块已退出并释放了流,因此Tokenize尝试从中读取的操作将失败)。
我可以承受对文件(打开的流)进行相对较长时间的锁定,但迟早必须关闭它。由于我不能真正使用using,因此必须显式处理流的释放。
问题是:我应该在哪里调用Dispose()?是否有任何常见的惯用法来处理这些情况?我不想以“旧方式”(在几个地方释放资源,在代码的某个地方更改时需要重新审查每个释放等)的方式进行操作。
我的第一个想法是使Log类可处置,因此其构造函数可以接受文件名并在类内进行所有资源管理(仅要求使用者在完成后处理Log本身),但我看不到在调用基类构造函数之前创建和保存流的方法(流是为生成器对象所需的参数而调用的)。
注意:除非绝对必要,否则不应触及CachingProxyList(我希望将其保持足够通用以使其可重复使用)。特别是,构造函数对于强制执行一些实现中严重依赖的不变量至关重要(例如内部枚举器对象永远不为空)。另一方面,其他所有内容都应公平竞争。
感谢您的耐心阅读,如果您已经阅读了这篇文章,也感谢您提供的任何帮助。 ;)
1个回答

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

上述语句可能有例外,但这是一般规则。例如StreamWriter使用流(实现了IDisposable接口),这迫使其本身实现IDisposable接口-因为它不知道何时处置它。

在您的情况下是相同的。您的类在使用可处理的资源时并不知道何时处理它 - 或者我认为是这样。这将使它实现IDisposable接口。您的Log类的客户端将不得不在您的类上调用Dispose()

因此,正如您所看到的,这变成了一个链,而非可处理的客户端将不得不在其使用的资源上调用处理,并且该资源将处理其资源等等...


这虽然微妙,但却导致了一个解决方案:它将问题转化为决定谁/什么应该拥有流,我猜测Tokenize方法是最佳候选人(毕竟它是唯一实际使用它们的方法)。"所有者负责调用资源的Dispose()方法"明确回答了问题,而"每个资源必须有一个所有者"则使问题的根源显而易见。谢谢! - Edurne Pascual
关于您的编辑说明:正如我在问题中提到的,将Log类设为可处理的是我的第一个想法,但存在一些问题(将流传递给Tokenize,但也要保存它以供稍后处理)。实际解决方案将是将文件名本身一直传递到Tokenize,然后该方法将创建、拥有和处理流。顺便说一句,我认为突出“一个所有者”的观点是个好主意:这一直是解决问题的关键。 - Edurne Pascual
+1. herenvardo,如果流(或任何类似的资源)由其他东西拥有,则不必在打开流时使用“using”也是可以的。将文件名传递给您的日志类将使其同时负责两件事-打开文件和读取日志,请考虑将流实际传递以分担职责。至少请考虑拥有两种版本(检查XmlWriter.Create方法以获取可能的方法)。 - Alexei Levenkov
@Alexei:感谢您的评论,但我强烈不喜欢从未创建资源的代码块中处理资源的想法。这实际上与“拥有一个所有者”的概念相吻合:创建和处理它的代码将不可避免地成为所有者。既然您提到了它,我的Log类实际上除了在其他代码之间分配工作(缓存到基类,文件I/O到标记器,解析到解析器等)之外,没有任何责任。 - Edurne Pascual
我可以将Tokenize方法拆分(因此另一个方法负责打开、读取和处理流,它只是将读取的数据拆分为标记),但我不确定是否值得这样做。 - Edurne Pascual

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接