1

次の方法を検討してください。

IEnumerable<DateTime> GetTimes(int count)
{
 for (int i = 0; i < count; i++)
      yield return DateTime.Now;
 yield break;
}

今、私はそれを呼びたいです:

 var times = GetTimes(2);
 Console.WriteLine("First element:" + times.Take(1).Single().ToString());
 Console.WriteLine("Second element:" + times.Skip(1).Take(1).Single().ToString());
 Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString());
 Console.WriteLine("Finished...");

ただし、コードの最後の行は実行されません。なんで?

4

3 に答える 3

2

インターレーターの仕組みが原因で、ラインyield break;が実行されることはありません。

あなたがそうするとき、イテレータメソッドGetTimes(int count)は実行されません

var times = GetTimes(2);

むしろ、そこから値を取得するたびに実行されます (たとえば、 を実行するときtimes.Take(1).Single().ToString())。

ここで、この一見奇妙な動作を生成するものは 2 つあります。

  1. yield returnイテレータは、行にヒットするたびに停止します。イテレータの実行は、そこから別の要素を取得しようとすると、終了したポイントから再開されます。そうしないと、実行が再開されません。

  2. 実際にはイテレータを 2 回実行しています。通常、 「IEnumerable の複数の列挙」と呼ばれることを行います。

バックグラウンドで実際に何が起こっているかを説明するために、GetTimesメソッドを少し変更してみましょう: 毎回同じ日付を返すのではなく、呼び出されるたびに前の日付 + 1 日を返します。Console.WriteLine実行をトレースするためにいくつか追加してみましょう。したがって、新しいメソッド本体は次のようになります。

IEnumerable<DateTime> GetTimes(int count)
{
    for (int i = 0; i < count; i++)
    {
        Console.WriteLine("returning the value with index " + i);
        yield return DateTime.Now.AddDays(i);
    }

    Console.WriteLine("About to hit the `yield break`! Awesome!");
    yield break;
}

コードを実行すると、次の出力が生成されます。

インデックス 0 の値を返す
最初の要素:11/16/2012 11:34:46 PM
インデックス 0 の値を返す
インデックス 1 の値を返す
2 番目の要素:11/17/2012 11:34:46 PM
終了した...

これは、上記の 2 つの点を示しています。

  1. GetTimes値が返されるとすぐに実行が停止され、別の値が要求されるとすぐに同じ状態から再開されます。

  2. Iterator は 2 回実行されます (2 回目に値を要求し、次の値を要求して使用timesします)。SkipTake

わかりましたが、なぜyield break;get が実行されないのですか? これは、イテレータが 2 つの値を生成でき、2 回しか呼び出されないため、2 回目のyield returnヒット後にフリーズするためです。イテレータから 3 番目の要素を要求すると、break行がヒットします。

foreachここで、最後の行にヒットするシナリオを説明するために、列挙子を通常の方法 (ループを使用) で使用してみましょう。Console.WriteLine行を次のように置き換えます。

foreach (var dateTime in times)
    Console.WriteLine(dateTime);

このコードは、次の出力を生成します。

インデックス 0 の値を返す
2012/11/17 12:05:20 午前
インデックス 1 の値を返す
2012/11/18 12:05:20 午前
「利回りブレーク」を迎えようとしています!素晴らしい!

ご覧のとおりforeach、イテレータが最後まで消費され、yield break行がヒットします。これは、ブレークポイントを設定して確認することもできます。

于 2012-11-16T21:42:02.870 に答える
1

列挙子の最後の行が実行されないことを意味すると仮定すると...行

yield break;

(コードの初期バージョンで)2つの要素を持つシーケンスから2つの要素のみを取得するため、は実行されません。列挙子の背後にあるステートマシンは、その行を実行するのに十分な距離にはなりません。

シーケンスから3つのyield break;要素を取得しようとすると、実際に実行されます。

呼び出し元のコードの最後の行を呼び出さない理由はわかりません(例外がスローされる可能性があります)。

Console.WriteLine("Finished...");

それがあなたの言いたいことなら、例外がスローされていますか?もしそうなら、例外の性質は何ですか?

以前の誤った更新の書き換え

最初に書かれたコードその行を実行します

Console.WriteLine("Finished...");

実行されません

yield break;

前述の理由で。

その後質問に追加された行

Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString());

成功せず、実際にInvalidOperationException( "シーケンスに要素が含まれていません")をスローし、yield break;2つしかないシーケンスから3つの要素を取得しようとするため、行を実行します。

更新2

イテレータブロックとYieldキーワードがどのように機能するかを理解しようとしている場合は、Eric Lippert(Visual C#チームの主任開発者)による一連のブログ投稿を強くお勧めします。

http://blogs.msdn.com/b/ericlippert/archive/2009/07/09/iterator-blocks-part-one.aspx

于 2012-11-16T21:14:27.723 に答える
0

「3 番目の要素」(シーケンス内の 2 つのうち) を取得しようとすると、.Single()呼び出しには何も返されないため、例外がスローされます。

ところで、その方法をご存知.ElementAt(int)ですか?なぜそれを使わないのですか?

于 2012-11-16T21:31:39.137 に答える