3

さて、これは悪いコードの良い部分です:

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<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は公正なゲームでなければなりません。

これをお読みいただきありがとうございます。また、提供されたヘルプについても事前に感謝します;)

4

1 に答える 1

5
  • アンマネージ リソースをカプセル化するクラスは、破棄パターン (IDisposableインターフェイス) を実装する必要があります。たとえば、ストリーム、データベース接続など
  • すべてのリソースには1 人の所有者が必要です
  • Dispose()所有者はリソースを呼び出す責任があります
  • 所有者がそのリソースをすぐに呼び出せない場合Dispose()、または呼び出すタイミングがわからない場合は、IDisposableインターフェイス自体を実装Dispose()し、そこにあるリソースを呼び出す必要があります。

上記のステートメントには例外がありますが、それが一般的なルールです。例はStreamWriter、(インターフェースを実装する)ストリームを取り込んで、インターフェース自体IDisposableを強制的に実装することです。これは、いつ破棄するかわからないためです。IDisposable

あなたの場合も同じです。あなたのクラスは、いつ破棄するかわからないときに使い捨てのリソースを使用します-またはそれが私が想定していることです。これにより、IDisposableインターフェイスを実装できます。クラスのクライアントは、Logクラスを呼び出す必要がありDispose()ます。

ご覧のとおり、これはチェーンになりますが、非使い捨てクライアントは使用するリソースで dispose を呼び出す必要があり、そのリソースはそのリソースを破棄します...

于 2011-04-05T23:35:52.257 に答える