12

コードブロックがある場合

static void Main()
{

  foreach (int i in YieldDemo.SupplyIntegers())
  {
    Console.WriteLine("{0} is consumed by foreach iteration", i);
  }
}


 class YieldDemo
  {
    public static IEnumerable<int> SupplyIntegers()
     {
         yield return 1;
         yield return 2;
          yield return 3;
       }
   }

イールドリターンの背後にある原理を次のように解釈できますか?

  1. Main() は SupplyIntegers() を呼び出します
  2. |1| |2| |3| are stored in contiguous memory block.Pointer of "IEnumerator" Moves to |1|
  3. 制御は SupplyInteger() から Main() に戻ります。
  4. Main() は値を出力します
  5. ポインター |2| などに移動します。

説明:

(1) 通常、関数内には 1 つの有効な return ステートメントが許可されます。複数の yield return、yield return、... ステートメントが存在する場合、C# はどのように処理しますか?

(2) リターンが発生すると、制御が再び SupplyIntegers() に戻る方法はありません。許可されている場合、Yield は再び 1 から始まりませんか? つまり、利回りは1ですか?

4

4 に答える 4

34

いいえ-それからはほど遠いです。長文バージョンを書きます... 汚すぎます!


foreachが実際には次のことを理解している場合にも役立つことに注意してください。

using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) {
    int i;
    while(iterator.MoveNext()) {
        i = iterator.Current;
         Console.WriteLine("{0} is consumed by foreach iteration", i);
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
static class Program
{
    static void Main()
    {

        foreach (int i in YieldDemo.SupplyIntegers())
        {
            Console.WriteLine("{0} is consumed by foreach iteration", i);
        }
    }
}

 class YieldDemo
  {

    public static IEnumerable<int> SupplyIntegers()
     {
         return new YieldEnumerable();
       }
    class YieldEnumerable : IEnumerable<int>
    {
        public IEnumerator<int> GetEnumerator()
        {
            return new YieldIterator();
        }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    }
    class YieldIterator : IEnumerator<int>
    {
        private int state = 0;
        private int value;
        public int Current { get { return value; } }
        object IEnumerator.Current { get { return Current; } }
        void IEnumerator.Reset() { throw new NotSupportedException(); }
        void IDisposable.Dispose() { }
        public bool MoveNext()
        {
            switch (state)
            {
                case 0: value = 1; state = 1;  return true;
                case 1: value = 2; state = 2;  return true;
                case 2: value = 3; state = 3; return true;
                default: return false;
            }
        }
    }
}

ご覧のとおり、イテレータでステート マシンが構築され、ステート マシンは で進行しMoveNextます。stateこれがより複雑なイテレータでどのように機能するかを見ることができるので、フィールドでパターンを使用しました。

重要:

  • イテレータ ブロック内のすべての変数は、ステート マシンのフィールドになります
  • finallyブロック( を含むusing)がある場合、それはDispose()
  • につながるコードの部分yield return(case大まかに)
  • yield breakstate = -1; return false;(または同様の) になります

C# コンパイラがこれを行う方法は非常に複雑ですが、イテレータを簡単に記述できます。

于 2009-10-20T19:44:57.163 に答える
3

これは単なる構文シュガーです。.net は IEnumerator クラスを生成し、MoveNext、Current、および Reset メソッドを実装します。さらに、その IEnumerator を返す IEnumarable クラス GetEnumerator を生成します。.net リフレクターまたは ildasm でその魔法のクラスを確認できます。

こちらもご覧ください

于 2009-10-20T19:47:34.267 に答える
2

簡単に言えば、イテレータ ブロック (またはyieldステートメントを含むメソッド) は、コンパイラによってコンパイラによって生成されたクラスに変換されます。このクラスが実装IEnumeratorし、yieldステートメントがそのクラスの「状態」に変換されます。

たとえば、これは次のとおりです。

yield return 1;
yield return 2;
yield return 3;

次のようなものに変換される場合があります。

switch (state)
{
    case 0: goto LABEL_A;
    case 1: goto LABEL_B;
    case 2: goto LABEL_C;
}
LABEL_A:
    return 1;
LABEL_B:
    return 2;
LABEL_C:
    return 3;

イテレータ ブロックは、抽象化されたステート マシンと見なすことができます。IEnumeratorこのコードはのメソッドによって呼び出されます。

于 2009-10-20T19:56:27.733 に答える
1

つまり、(marc のロングハンド バージョンを待っている間) コンパイラが yield ステートメントを確認すると、舞台裏で、 と呼ばれるインターフェイスを実装するカスタム クラスの新しいインスタンスを構築しますIEnumerator。反復プロセスの現在の場所...上記の例では、列挙されるリスト内の値も追跡します。Current()MoveNext()

于 2009-10-20T19:52:16.853 に答える