11

「最後の」割り当てで次のエラー メッセージ (テキスト リーダーのクローズド例外) がスローされる理由が気になります。

IEnumerable<string> textRows = File.ReadLines(sourceTextFileName);
IEnumerator<string> textEnumerator = textRows.GetEnumerator();

string first = textRows.First();
string last = textRows.Last();

ただし、以下は正常に実行されます。

IEnumerable<string> textRows = File.ReadLines(sourceTextFileName);

string first = textRows.First();
string last = textRows.Last();

IEnumerator<string> textEnumerator = textRows.GetEnumerator();

異なる動作の理由は何ですか?

4

1 に答える 1

12

私が知る限り、あなたはフレームワークにバグを発見しました。いくつかのことが相互作用するため、かなり微妙です。

  • を呼び出すReadLines()と、ファイルが実際に開かれます。個人的には、これ自体がバグだと考えています。私はそれが怠惰であることを期待し、望んでいます-反復処理を開始しようとしたときにのみファイルを開きます。
  • の戻り値で初めて呼び出すとGetEnumerator()、実際には同じ参照が返されます。ReadLines
  • First()呼び出すとGetEnumerator()、クローンが作成されます。StreamReaderこれは同じものを共有しますtextEnumerator
  • First()クローンを破棄すると、 が破棄され変数が にStreamReader設定されます。これは元の変数には影響しません。現在は破棄された変数を参照しています。nullStreamReader
  • Last()呼び出すと、元のオブジェクトGetEnumerator()のクローンが作成され、破棄が完了します。次に、そのリーダーから読み取ろうとし、例外をスローします。StreamReader

これを 2 番目のバージョンと比較します。

  • First()呼び出すGetEnumerator()と、元の参照が返され、リーダーが開かれます。
  • その後First()が呼び出さDispose()れると、リーダーが破棄され、変数がに設定されますnull
  • Last()呼び出すGetEnumerator()と、クローンが作成されますが、クローンの値にはnull参照があるため、新しいStreamReaderものが作成されるため、問題なくファイルを読み取ることができます。次に、クローンを破棄し、リーダーを閉じます
  • GetEnumerator()呼び出されると、元のオブジェクトの 2 番目のクローンが作成され、さらに別のオブジェクトが開かれますStreamReader。これも問題ありません。

基本的に、最初のスニペットの問題は、最初のオブジェクトを破棄せずにGetEnumerator()( で) 2 回目の呼び出しを行っていることです。First()

同じ問題の別の例を次に示します。

using System;
using System.IO;
using System.Linq;

class Test
{
    static void Main()
    {
        var lines = File.ReadLines("test.txt");
        var query = from x in lines
                    from y in lines
                    select x + "/" + y;
        foreach (var line in query)
        {
            Console.WriteLine(line);
        }
    }
}

これを修正するには、 を 2 回呼び出すか、次のようFile.ReadLinesに の本当に怠惰な実装を使用します。ReadLines

using System.IO;
using System.Linq;

class Test
{
    static void Main()
    {
        var lines = ReadLines("test.txt");
        var query = from x in lines
                    from y in lines
                    select x + "/" + y;
        foreach (var line in query)
        {
            Console.WriteLine(line);
        }
    }

    static IEnumerable<string> ReadLines(string file)
    {
        using (var reader = File.OpenText(file))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }
}

後者のコードでは、が呼び出されるStreamReaderたびに新しいファイルが開かGetEnumerator()れるため、結果は test.txt の各行のペアになります。

于 2012-12-26T11:11:32.840 に答える