さて、これは悪いコードの良い部分です:
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<T>
は、カスタムの「キャッシング」列挙子を提供するの実装です。IEnumerable<T>
コンストラクターを受け取り、最初はそれを列挙しますが、各項目をプライベートList<T>
フィールドに保存するため、実際の解析に進む前にそれをさらに反復します (むしろときどき解析する; または、その小さな部分を照会するためだけに巨大なログを解析する必要がある)。
この最適化は実際には必要であり、そのほとんどは既に機能していることに注意してください (ステートメントを削除すると、using
ファイル ハンドルのリークを除いてすべてがうまくいきます)。
Parse
との両方Tokenize
がイテレータブロックです(私の知る限り、実行を延期し、同時にコードをきれいにすることができる唯一の正気の方法です)。それらの署名はIEnumerable<Event> Parse(IEnumerable<Token>)
とIEnumerable<Token> Tokenize(StreamReader)
です。彼らの論理は問題とは無関係です。
論理的な流れは非常に明確です。そして、コードの各部分の意図はかなり明白です。しかし、これらのusing
ブロックは、遅延実行全体とはうまくいきません (オブジェクトを列挙するまでに、Log
オブジェクトusing
は既に終了しており、ストリームは破棄されているため、Tokenize
それから読み込もうとすると悲惨なクラッシュが発生します)。
比較的長い間、ファイル (開いているストリーム) をロックする余裕はありますが、遅かれ早かれ、ファイルを閉じる必要があります。sは実際には使用できないため、using
明示的にストリームを破棄する必要があります。
問題は、呼び出しをDispose()
どこに置くべきかということです。このようなシナリオに対処するための一般的なイディオムはありますか? 私はこれを「古い方法」で行いたくありません (いくつかの場所でリソースをリリースし、どこかでコードが少し変更されるたびにリリースごとにレビューしなければならない、など)。
私の最初のアイデアは、Log
クラスを使い捨てにすることでした。そのため、そのコンストラクターはファイル名を取り、クラス内のすべてのリソース管理を行うことができました (Log
完了時に消費者のみがそれ自体を破棄する必要があります)。しかし、作成する方法がわかりません。コンストラクターを呼び出す前にストリームを保存しますbase
(ストリームは、そのコンストラクターの引数を生成する呼び出しに必要です)。
注:CachingProxyList
厳密に必要でない限り、触れないでください(再利用できるように十分に一般的なものにしたいです)。特に、コンストラクターは、実装の残りの部分が大きく依存するいくつかの不変条件 (内部の列挙子オブジェクトが決して null にならないなど) を強制するために不可欠です。他のすべて、OTOHは公正なゲームでなければなりません。
これをお読みいただきありがとうございます。また、提供されたヘルプについても事前に感謝します;)
。