Disposing of arguments for an iterator block

天大地大妈咪最大 提交于 2019-12-23 18:31:08

问题


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 ;).


回答1:


  • 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...



来源:https://stackoverflow.com/questions/5559781/disposing-of-arguments-for-an-iterator-block

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!