问题
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 using
s, 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 implementIDisposable
interface itself and callDispose()
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