Trouble understanding yield in C#

geostocker picture geostocker · Jun 28, 2017 · Viewed 9.1k times · Source

I'm hoping to get some clarification on a snippet that I've recently stepped through in the debugger, but simply cannot really understand.

I'm taking a C# course on PluralSight and the current topic is on yield and returning a IEnumerable<T> with the keyword.

I've got this overly basic function that returns an IEnumerable collection of Vendors (A simple class with Id, CompanyName and Email):

public IEnumerable<Vendor> RetrieveWithIterator()
{
    this.Retrieve(); // <-- I've got a breakpoint here
    foreach(var vendor in _vendors)
    {
        Debug.WriteLine($"Vendor Id: {vendor.VendorId}");
        yield return vendor;
    }
}

And I've got this code in a unit test that I'm using to test the function:

var vendorIterator = repository.RetrieveWithIterator(); // <-- Why don't it enter function?
foreach (var item in vendorIterator) // <-- But starts here?
{
    Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();

What I really can't seem to understand, and I'm sure a lot of beginners are having the same trouble, is why the initial call to RetrieveWithIterator doesn't initiate the function, but it rather starts when we start iterating through its returned IEnumerable collection (see the comments).

Answer

InBetween picture InBetween · Jun 28, 2017

This is called deferred execution, yield is lazy and will only work as much as it needs to.

This has great many advantages, one of which being that you can create seemingly infinite enumerations:

public IEnumerable<int> InfiniteOnes()
{
     while (true)
         yield 1;
}

Now imagine that the following:

var infiniteOnes = InfiniteOnes();

Would execute eagerly, you'd have a StackOverflow exception coming your way quite happily.

On the other hand, because its lazy, you can do the following:

var infiniteOnes = InfiniteOnes();
//.... some code
foreach (var one in infiniteOnes.Take(100)) { ... }

And later,

foreach (var one in infiniteOnes.Take(10000)) { ... }

Iterator blocks will run only when they need to; when the enumeration is iterated, not before, not after.