10

ストリームを最初から最後まで2回読み取る必要があります。

ただし、次のコードは例外をスローしObjectDisposedException: Cannot access a closed fileます。

string fileToReadPath = @"<path here>";
using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open))
{
    using (StreamReader reader = new StreamReader(fs))
    {
        string text = reader.ReadToEnd();
        Console.WriteLine(text);
    }

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException thrown.

    using (StreamReader reader = new StreamReader(fs))
    {
        string text = reader.ReadToEnd();
        Console.WriteLine(text);
    }
}

なぜそれが起こっているのですか?本当に何が処分されますか?そして、なぜ操作StreamReaderがこのように関連するストリームに影響を与えるのでしょうか?シーク可能なストリームは、数秒を含めて数回読み取ることができると期待するのは論理的ではありませんStreamReaderか?

4

6 に答える 6

16

これStreamReaderは、ストリームの「所有権」を引き継ぐために発生します。つまり、ソースストリームを閉じる責任があります。Disposeプログラムがまたは(あなたの場合はステートメントスコープをClose残して)呼び出すとすぐに、ソースストリームも破棄されます。usingあなたの場合に電話fs.Dispose()します。usingしたがって、最初のブロックを離れた後、ファイルストリームは停止します。これは一貫した動作であり、別のストリームをラップする.NETのすべてのストリームクラスはこのように動作します。

ソースストリームを所有していないStreamReaderと言うことができるコンストラクタが1つあります。ただし、.NETプログラムからはアクセスできず、コンストラクターは内部にあります。

この特定のケースでは、にusing-statementを使用しないことで問題を解決しますStreamReader。ただし、これはかなり厄介な実装の詳細です。確かにより良い解決策がありますが、コードは合成的すぎて実際の解決策を提案できません。

于 2010-10-10T18:43:46.897 に答える
7

の目的はDispose()、ストリームの終了時にリソースをクリーンアップすることです。リーダーがストリームに影響を与える理由は、リーダーがストリームをフィルタリングしているだけであるため、リーダーを破棄することは、ソースストリームへの呼び出しをチェーンする場合を除いて意味がありません。

コードを修正するには、常に1つのリーダーを使用します。

using (FileStream fs = new FileStream(fileToReadPath, FileMode.Open))
using (StreamReader reader = new StreamReader(fs))
{
    string text = reader.ReadToEnd();
    Console.WriteLine(text);

    fs.Seek(0, SeekOrigin.Begin); // ObjectDisposedException not thrown now

    text = reader.ReadToEnd();
    Console.WriteLine(text);
}

以下のコメントに対処するために編集

ほとんどの場合、コード(fs.Seek)で行うように、基になるストリームにアクセスする必要はありません。このような場合、基になるストリームへの呼び出しをチェーンするという事実により、ストリームのステートメントをStreamReaderまったく使用しないことで、コードを節約できます。usingsたとえば、コードは次のようになります。

using (StreamReader reader = new StreamReader(new FileStream(fileToReadPath, FileMode.Open)))
{
    ...
}
于 2010-10-10T18:12:53.513 に答える
2

Usingスコープを定義し、その外側でオブジェクトが破棄されるため、ObjectDisposedException. このブロックの外から StreamReader のコンテンツにアクセスすることはできません。

于 2010-10-10T18:15:38.267 に答える
1

あなたの質問に同意します。この意図的な副作用の最大の問題は、開発者がそれを知らず、StreamReader をusing. しかし、それが長命のオブジェクトのプロパティにある場合、バグを追跡するのが非常に困難になる可能性があります.私が見た最高の(最悪の?)例は

using (var sr = new StreamReader(HttpContext.Current.Request.InputStream))
{
    body = sr.ReadToEnd();
}

開発者は、InputStream が存在することを期待する将来の場所に接続されていることを知りませんでした。

明らかに、内部構造がわかれば、回避しusingて位置を読み取ってリセットすることがわかります。しかし、API 設計の核となる原則は、副作用を回避すること、特に操作対象のデータを破壊しないことだと思いました。「リーダー」であると思われるクラスに固有のものは、それを「使用」したときに読み取ったデータをクリアする必要はありません。リーダーを破棄すると、ストリーム自体をクリアするのではなく、ストリームへの参照を解放する必要があります。私が考えることができる唯一のことは、リーダーがシークポインターの位置のようなストリームの他の内部状態を変更しているため、選択を行う必要があったということです。すべてが完了します。一方、あなたの例のように、ストリームを作成している場合、using、ただし、即時メソッドの外部で作成されたストリームを読み取っている場合、データをクリアするコードは傲慢です。

読み取りコードが明示的に作成しない Stream インスタンスで私が行っていること、および開発者に行うように指示していることは...

// save position before reading
long position = theStream.Position;
theStream.Seek(0, SeekOrigin.Begin);
// DO NOT put this StreamReader in a using, StreamReader.Dispose() clears the stream
StreamReader sr = new StreamReader(theStream);
string content = sr.ReadToEnd();
theStream.Seek(position, SeekOrigin.Begin);

(申し訳ありませんが、これを回答として追加しました。コメントに収まりません。フレームワークのこの設計上の決定についてもっと議論したいと思います)

于 2012-10-22T19:41:51.820 に答える
0

Dispose()Dispose()のすべてのストリームが所有されます。残念ながら、ストリームにはDetach()メソッドがないため、ここで回避策を作成する必要があります。

于 2010-10-10T18:52:00.117 に答える
0

理由はわかりませんが、StreamReader を未処理のままにしておくことができます。そうすれば、StreamReader が収集された場合でも、基になるストリームは破棄されません。

于 2010-10-10T19:08:12.473 に答える