Can someone demystify the yield keyword?

I have seen the yield keyword being used quite a lot on Stack Overflow and blogs. I don't use LINQ. Can someone explain the yield keyword?

I know that similar questions exist. But none really explain what is its use in plain simple language.


By far the best explanation of this (that I've seen) is Jon Skeet's book - and that chapter is free! Chapter 6, C# in Depth. There is nothing I can add here that isn't covered.

Then buy the book; you will be a better C# programmer for it.


Q: Why didn't I write a longer answer here (paraphrased from comments); simple. As Eric Lippert observes (here), the yield construct (and the magic that goes behind it) is the single most complex bit of code in the C# compiler , and to try and describe it in a brief reply here is naïve at best. There are so many nuances to yield that IMO it is better to refer to a pre-existing (and fully qualified) resource.

Eric's blog now has 7 entries (and that is just the recent ones) discussing yield . I have a vast amount of respect for Eric, but his blog is probably more appropriate as a "further information" for people who are comfortable with the subject ( yield in this case), as it typically describes a lot of the background design considerations. Best done in the context of a reasonable foundation.

(and yes, chapter 6 does download; I verified...)


The yield keyword is used with methods that return IEnumerable<T> or IEnumerator<T> and it makes the compiler generate a class that implements the necessary plumbing for using the iterator. Eg

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

Given the above the compiler will generate a class that implements IEnumerator<int> , IEnumerable<int> and IDisposable (actually it will also implement the non-generic versions of IEnumerable and IEnumerator ).

This allows you to call the method SequenceOfOneToThree in a foreach loop like this

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

An iterator is a state machine, so each time yield is called the position in the method is recorded. If the iterator is moved to the next element, the method resumes right after this position. So the first iteration returns 1 and marks that position. The next iterator resumes right after one and thus returns 2 and so forth.

Needless to say you can generate the sequence in any way you like, so you don't have to hard code the numbers like I did. Also, if you want to break the loop you can use yield break .


In an effort to demystify I'll avoid talking about iterators, since they could be part of the mystery themselves.

the yield return and yield break statements are most often used to provide "deferred evaluation" of the collection.

What this means is that when you get the value of a method that uses yield return, the collection of things you are trying to get don't exist together yet (it's essentially empty). As you loop through them (using foreach) it will execute the method at that time and get the next element in the enumeration.

Certain properties and methods will cause the entire enumeration to be evaluated at once (such as "Count").

Here's a quick example of the difference between returning a collection and returning yield:

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)");

This can also be used if you need to get a reference to the Enumeration before the source data has values. For example if the names collection wasn't complete to start with:

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.");
链接地址: http://www.djcxy.com/p/9108.html

上一篇: 有些有助于理解“收益”

下一篇: 有人可以揭开yield关键字吗?