28

私は、yield キーワードが Stack Overflow やブログで非常に多く使用されているのを見てきました。LINQは使用しません。誰かがyieldキーワードを説明できますか?

同様の質問が存在することは知っています。しかし、平易で単純な言葉でその使用法を実際に説明しているものはありません。

4

9 に答える 9

32

これについて (私が見た中で) 群を抜いて最良の説明は、Jon Skeet の本です - そしてその章は無料です! 第 6 章、C# の詳細。ここに追加できるものでカバーされていないものはありません。

次に、本を購入します。あなたはより良い C# プログラマーになるでしょう。


Q: ここに長い回答を書かなかったのはなぜですか (コメントから言い換え); 単純。Eric Lippert が観察しているように (ここで)、yieldコンストラクト (およびその背後にある魔法) は、C# コンパイラのコードの中で最も複雑なビットであり、ここで簡単な返信でそれを説明しようとするのは、せいぜいナイーブです。その IMO には非常に多くのニュアンスがあるため、yield既存の (そして完全に修飾された) リソースを参照する方が適切です。

Eric のブログには現在 7 つのエントリがあります (これは最近のエントリです) yield。私はEric に多大な敬意を払っていますが、彼のブログは、通常、背景となる設計上の考慮事項の多くを説明しているため、このテーマに慣れている人 (このyield場合)のための「追加情報」としておそらくより適切です。合理的な基盤のコンテキストで行うのが最善です。

(そして、はい、第6章ダウンロードします;私は確認しました...)

于 2009-08-25T19:42:39.187 に答える
30

キーワードはoryieldを返すメソッドで使用され、イテレータを使用するために必要な配管を実装するクラスをコンパイラに生成させます。例えばIEnumerable<T>IEnumerator<T>

public IEnumerator<int> SequenceOfOneToThree() {
    yield return 1;
    yield return 2;
    yield return 3;
}

上記の場合、コンパイラは、、およびを実装するクラスを生成します(実際にはIEnumerator<int>、およびの非汎用バージョンも実装します)。IEnumerable<int>IDisposableIEnumerableIEnumerator

SequenceOfOneToThreeこれにより、このようなforeachループでメソッドを呼び出すことができます

foreach(var number in SequenceOfOneToThree) {
    Console.WriteLine(number);
}

イテレータはステートマシンであるため、呼び出されるたびyieldにメソッド内の位置が記録されます。イテレータが次の要素に移動すると、この位置の直後にメソッドが再開されます。したがって、最初の反復は1を返し、その位置をマークします。次のイテレータは1の直後に再開するため、2を返します。

言うまでもなく、シーケンスは任意の方法で生成できるため、私のように数値をハードコーディングする必要はありません。また、ループを解除したい場合は、を使用できますyield break

于 2009-08-25T19:50:07.220 に答える
18

謎を解き明かすために、イテレータについては話さないようにします。イテレータ自体が謎の一部である可能性があるからです。

yield return および yield break ステートメントは、コレクションの「遅延評価」を提供するために最もよく使用されます。

これが意味することは、yield return を使用するメソッドの値を取得するときに、取得しようとしているもののコレクションがまだ一緒に存在していない (本質的に空である) ということです。それらを(foreachを使用して)ループすると、その時点でメソッドが実行され、列挙内の次の要素が取得されます。

特定のプロパティとメソッドでは、列挙全体が一度に評価されます ("Count" など)。

コレクションを返すことと利回りを返すことの違いの簡単な例を次に示します。

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

// we're going to execute the GetYieldEnumerable() method
// but the foreach statement inside it isn't going to execute
var yieldNames = GetNamesEnumerable();

// now we're going to execute the GetList() method and
// the foreach method will execute
var listNames = GetList();

// now we want to look for a specific name in yieldNames.
// only the first two iterations of the foreach loop in the 
// GetYieldEnumeration() method will need to be called to find it.
if (yieldNames.Contains("Jim")
    Console.WriteLine("Found Jim and only had to loop twice!");

// now we'll look for a specific name in listNames.
// the entire names collection was already iterated over
// so we've already paid the initial cost of looping through that collection.
// now we're going to have to add two more loops to find it in the listNames
// collection.
if (listNames.Contains("Jim"))
    Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)");

これは、ソース データに値が含まれる前に Enumeration への参照を取得する必要がある場合にも使用できます。たとえば、最初から名前のコレクションが完全ではなかった場合:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

var yieldNames = GetNamesEnumerable();

var listNames = GetList();

// now we'll change the source data by renaming "Jim" to "Jimbo"
names[1] = "Jimbo";

if (yieldNames.Contains("Jimbo")
    Console.WriteLine("Found Jimbo!");

// Because this enumeration was evaluated completely before we changed "Jim"
// to "Jimbo" it isn't going to be found
if (listNames.Contains("Jimbo"))
    // this can't be true
else
   Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated.");
于 2009-08-25T20:42:56.943 に答える
10

キーワードは、を書くのyieldに便利な方法IEnumeratorです。例えば:

public static IEnumerator<int> Range(int from, int to)
{
    for (int i = from; i < to; i++)
    {
        yield return i;
    }
}

C#コンパイラによって、次のようなものに変換されます。

public static IEnumerator<int> Range(int from, int to)
{
    return new RangeEnumerator(from, to);
}

class RangeEnumerator : IEnumerator<int>
{
    private int from, to, current;

    public RangeEnumerator(int from, int to)
    {
        this.from = from;
        this.to = to;
        this.current = from;
    }

    public bool MoveNext()
    {
        this.current++;
        return this.current < this.to;
    }

    public int Current
    {
        get
        {
            return this.current;
        }
    }
}
于 2009-08-25T19:46:10.270 に答える
6

MSDNのドキュメントと例を見てください。基本的に、C# で反復子を作成するのは簡単な方法です。

public class List
{
    //using System.Collections;
    public static IEnumerable Power(int number, int exponent)
    {
        int counter = 0;
        int result = 1;
        while (counter++ < exponent)
        {
            result = result * number;
            yield return result;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }
}
于 2009-08-25T19:44:08.510 に答える
4

関数型プログラミングに関するEric White のシリーズは、全体を読む価値がありますが、Yield に関するエントリは、私が見た限り明確な説明です。

于 2009-08-25T19:44:06.500 に答える
3

yieldLINQ とは直接関係ありませんが、イテレータ ブロックとは関係ありません。リンクされた MSDN の記事では、この言語機能について詳しく説明しています。特にイテレータの使用セクションを参照してください。イテレータ ブロックの詳細については、この機能に関するEric Lippert の最近のブログ投稿を参照してください。一般的な概念については、イテレータに関するウィキペディアの記事を参照してください。

于 2009-08-25T19:44:21.303 に答える
2

リストを手動でディープ コピーする必要がある .NET の欠点を克服するために、これを思いつきました。

私はこれを使用します:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

そして別の場所で:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

これを行うワンライナーを考え出そうとしましたが、匿名メソッドブロック内でyieldが機能しないため、不可能です。

編集:

さらに良いことに、一般的な List クローンを使用します。

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
于 2009-09-30T09:52:49.780 に答える
0

このすべてに追加させてください。利回りはキーワードではありません。通常の変数のように機能する以外は、「yield return」を使用する場合にのみ機能します。

関数からイテレータを返すために使用します。その上でさらに検索できます。「Returning Array vs Iterator」を検索することをお勧めします

于 2009-08-25T19:55:09.567 に答える