处理迭代器块的参数
好吧,这是一段糟糕的代码:
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
和 IEnumerable
。他们的逻辑与问题无关。
逻辑流程非常清晰;并且代码各部分的意图相当明显;但是这些 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 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 ;)
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
IDisposable
接口)。例如流、数据库连接等Dispose()
Dispose()
在其资源上或者不知道何时调用它,那么它需要自己实现IDisposable
接口并在其资源上调用Dispose()
。上述陈述可能有例外,但这是一般规则。示例是
StreamWriter
,它接收一个流(实现了IDisposable
接口)并强制它本身实现IDisposable
接口 - 因为它不知道什么时候处理它。你的情况也是如此。您的类使用一次性资源,但它不知道何时处置它 - 或者这就是我的假设。这将使其实现 IDisposable 接口。
Log
类的客户端必须在您的类上调用Dispose()
。正如您所看到的,这变成了一条链,而非一次性客户端必须对其使用的资源调用 dispose,并且该资源将处置其资源,等等......
IDisposable
interface). For example stream, database connection, etcDispose()
on the resourceDispose()
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 implementIDisposable
interface) and that forces it to implementIDisposable
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 yourLog
class will have to callDispose()
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...