处理迭代器块的参数

发布于 2024-10-30 13:42:45 字数 1963 浏览 1 评论 0原文

好吧,这是一段糟糕的代码:

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 的实现,它提供了自定义“缓存”枚举器:它在其构造函数上采用 IEnumerable ,并最初枚举它,但将每个项目保存在私有 List 字段中,以便进一步迭代在继续实际解析之前(而不是时不时地解析;或者必须解析一个巨大的日志只是为了查询它的一小部分)。
请注意,这种优化实际上是需要的,并且其中大部分已经起作用(如果我删除 using 语句,除了泄漏文件句柄之外,一切都会正常)。

ParseTokenize 都是迭代器块(据我所知,这是我可以同时延迟执行和干净代码的唯一明智的方式);他们的签名是 IEnumerable; Parse(IEnumerable)IEnumerableTokenize(StreamReader)。他们的逻辑与问题无关。

逻辑流程非常清晰;并且代码各部分的意图相当明显;但是这些 using 块与整个延迟执行的事情并不相符(当我通过我的 Log 对象进行枚举时,using 已经退出并且流被释放,因此 Tokenize 尝试从中读取数据会严重崩溃)。

我可以承受在相对较长的时间内锁定文件(打开的流),但迟早我必须关闭它。由于我无法真正使用 using ,因此我必须显式处置流。

问题是:我应该在哪里调用 Dispose()?有什么常见的习惯用法来处理这样的场景吗?我不想用“旧方法”来做到这一点(在多个地方释放资源,必须在某个地方发生一点点代码更改时随时审查每个版本,等等)。

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

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

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

Allright, here it goes a good piece of bad code:

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

And now some context into the issue:

CachingProxyList is an implementation of IEnumerable<T> that provides a custom "caching" enumerator: it takes an IEnumerable<T> on its constructor, and initially enumerates through it, but saves each item on a private List<T> field so further iterations run through that before going on with the actual parsing (rather than parsing every now and again; or having to parse a huge log just to query a small portion of it).
Note that this optimization was actually needed, and most of it is already working (if I remove the using statements, everything goes fine except for the leaking file handles).

Both Parse and Tokenize are iterator blocks (AFAIK, the only sane way I could have deferred execution and clean code at the same time); their signatures are IEnumerable<Event> Parse(IEnumerable<Token>) and IEnumerable<Token> Tokenize(StreamReader). Their logics are unrelated to the issue.

The logical flow is quite clear; and the intent of each part of the code rather obvious; but those using blocks don't get along with the whole deferred execution thing (by the time I'm enumerating through my Log object, the using have already been exited and the stream disposed, so Tokenize's attempts to read from it miserably crash).

I can afford having a lock on the file (the open stream) for a relatively long time, but sooner or later I'll have to close it. Since I can't really use the usings, I'll have to explicitly dispose of the streams.

The question is: where should I put the calls to Dispose()? Is there any common idiom to deal with scenarios like these? I wouldn't like to do this the "old way" (releasing resources at several places, having to review each release anytime a tiny bit of the code changes somewhere, and so on).

My first idea was making the Log class disposable, so its constructor could take a file-name and have all the resource-management within the class (requiring only the consumer to dispose of the Log itself when done), but I can see no way of creating and saving the stream before calling the base constructor (the stream is required for the calls that yield the argument for that constructor).

Note: the CachingProxyList shouldn't be touched unless strictly needed (I want to keep it generic enough to make it reusable). Specially, the constructor is essential to enforce some invariants the rest of the implementation heavily relies in (such as the internal enumerator objects never being null). Everything else, OTOH, should be fair game.

Thanks for your patience if you have read this, and also thanks in advance for any help provided ;).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

晨敛清荷 2024-11-06 13:42:45
  • 封装非托管资源的类需要实现处置模式(IDisposable 接口)。例如流、数据库连接等
  • 每个资源必须有一个所有者
  • 所有者负责在资源上调用 Dispose()
  • 如果所有者无法立即调用 Dispose() 在其资源上或者不知道何时调用它,那么它需要自己实现 IDisposable 接口并在其资源上调用 Dispose()

上述陈述可能有例外,但这是一般规则。示例是 StreamWriter,它接收一个流(实现了 IDisposable 接口)并强制它本身实现 IDisposable 接口 - 因为它不知道什么时候处理它

你的情况也是如此。您的类使用一次性资源,但它不知道何时处置它 - 或者这就是我的假设。这将使其实现 IDisposable 接口。 Log 类的客户端必须在您的类上调用 Dispose()

正如您所看到的,这变成了一条链,而非一次性客户端必须对其使用的资源调用 dispose,并且该资源将处置其资源,等等......

  • Classes that encapsulate unmanaged resources need to implement dispose pattern (IDisposable interface). For example stream, database connection, etc
  • Every resource must have one owner
  • Owner is responsible for calling Dispose() on the resource
  • If owner cannot immediately call Dispose() on its resource or does not know when to call it, then it needs to implement IDisposable interface itself and call Dispose() on its resource in there.

Above statements could have exceptions but that is the general rule. Example is StreamWriter which takes in a stream (which implement IDisposable interface) and that forces it to implement IDisposable interface itself - since it does not know when to dispose it.

It is the same in your case. Your class uses a disposable resource while it does not know when to dispose it - or that is what I assume. This would make it to implement IDisposable interface. Client of your Log class will have to call Dispose() on your class.

So as you can see, this becomes a chain while the non-disposable client will have to call dispose on the resource it uses and that resource will dispose its resource, etc...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文