Click here to Skip to main content
Click here to Skip to main content

Pick Your Enumerator & Me.Understand Yield and IEnumerable (C#)

, 21 Apr 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Using multiple enumerators and implementing IEnumerable with Yield or IEnumerator.

Screenshot showing an enumerated DataGridView

Introduction

I am developing an unfinished linear algebra (matrix) class and wanted to provide the ability to enumerate it horizontally (by row) or vertically (by column). So I tried to implement IEnumerable, this requires implementing a method called GetEnumerator(). Then it can get a little confusing.

  • Creating a second class that implements IEnumerator and returning a new instance of it from the GetEnumerator() method isn't that hard
    • Creating more than one isn't any more difficult (vertical and horizontal) and choosing between them using a property is straightforward
  • But what about yield, how exactly do you use it? Is is 'better'? Do I use it in the matrix class or the IEnumerator class?
  • Can you use yield and still have more than one way of enumerating?

I tend to find that articles on this subject (and many others) start simple enough and then get more complex / theoretical / detailed than I would like. I would prefer basic, practical advice and guidance with a smattering of theory and explanation, I can learn bit by bit, not all at once. I am not Sheldon. In fact I wasn't completely sure until I'd finished writing the attached demo app, then I understood it (on a practical basis). So, we will look at implementing IEnumerable, by creating IEnumerator classes or by the use of yield - and we will compare the two approaches. I will also mention at least one other way of making a class enumerable (without explicitly implementing IEnumerable). We will also see two ways of allowing a user of a class to choose from n different ways of enumerating that class (as many as you implement), one with yield and one with IEnumerators.

If you are simply looking to understand (as I was) if yield is an alternative to creating IEnumerator classes (rather than something complementary for example) then the answer is: yes; although there are differences which are probably not trivial in many cases, we'll have a short look at this.

What is Yield?

If you arrived here trying to work out what yield is / does then here's my take.

When you use an Iterator method with yield it doesn't execute to its natural conclusion, every time it encounters a yield it does the same as a return statement and then stops right where it is, the method doesn't finish, then next time you call the method, it picks up where it left off, i.e. it does not start afresh each time you call it. So during a foreach loop these methods will run from start to finish once only, not once for each value in a collection, array or other grouping of data. It's a method that returns values many times not once - magic (or perhaps not magic, just the compiler saving us time since behind the scenes(^) it apparently creates IEnumerator like code anyway...)

Who is it for?

  1. Beginners
  2. Anyone who hasn't implemented IEnumerable, (via an IEnumerator or yield) before and wants an example
  3. Anyone who wants to implement more than one enumerator for an IEnumerable class
  4. Anyone who hasn't managed to get their head around one of the million other explanations out there; this is just my addition to that collection and I hope you find it more digestable than the others you've read so far, if not then no shakes, you have 999,999 other options!

Quick Summary

  1. We can use yield as an alternative to creating an IEnumerator class whilst implementing IEnumerable (and specifically the GetEnumerator() method)
  2. In some situations, particularly at moderate to high levels of complexity, yield can become orders of magnitude easier to code and understand / read than an IEnumerator class
  3. A class that is IEnumerable can have more than one enumerator, user selectable at run time
  4. You can make a class enumerable without declaring it IEnumerable but by implementing a method with a type of IEnumerable
    1. Implementing more than one method that has an IEnumerable return type has the same effect as #3
  5. When you use yield, the compiler creates an IEnumerator class, so in some ways it's no different, but the code you write may well be easier to read (efficiency of the code is a different question), see the last section

Warning 1: If you like your patterns and principles then even in code this short I have no doubt I've broken many. C'est la vie!

Warning 2: If there is a lack of detail in the semantics, then you have my apologies. I am writing from a practical perspective and sometimes to keep it simple, practical and readable I intentionally (or unintentionally) skip over the detail. For this reason the way two different people interpret the missing detail might be different. If you need or want to get that detail absolutely correct then there is masses of such information out there and I'm not going to try and replicate it here, that's not the intention. If there are clear written errors then I'm very happy to correct them.

Contents

Background

The genesis of this article was (is) an unfinished linear alegbra (matrix) project. I wanted to provide the ability to traverse the matrix by column (vertically) or by row (horizontally) which meant implementing IEnumerable. OK, so how do we do that then? Well, we start with the classic Google search. A great deal of somewhat frustrating time later I had a good idea I could use the yield keyword or I could create a class that implemented IEnumerator, or maybe I had to do both, could do both, didn't have to do either or had to do something else entirely; who knew?

Ultimately you can use yield or create a class that implements IEnumerator, your choice. There may be things that each is better suited for, I don't know enough yet to say one way or the other. As for my matrix class I can also implement 2 enumerators (or 10 if I really wanted), each doing something different and which can be interchanged at run time.

The Basics of Enumerating

The 'classic' (or 'old' if you prefer new shiny things) way of implementing IEnumerable is fairly straight forward so I'll start with a verbal description and then add the code .

The IEnumerable interface simply demands you implement a GetEnumerator() method that returns an object that has implemented IEnumerator. Then IEnumerator demands you implement several methods, the two key ones are the MoveNext() method and the Current property.

So imagine you use a foreach loop on a object that has implemented IEnumerable, such as this:

foreach (double element in EnumerableMatrix)
{
    Console.WriteLine(element.ToString());
}

What's actually happening is that the GetEnumerator() method is called on the EnumerableMatrix object. This returns a new object which we know must have implemented IEnumerator. This IEnumerator object can then be looped over using the MoveNext() method and the Current property of IEnumerator. In fact the code above is shorthand for this.

IEnumerator ObjectToEnumerate = EnumerableMatrix.GetEnumerator()
while (while ObjectToEnumerate.MoveNext() == True)
{
   Console.WriteLine(ObjectToEnumerate.Current.ToString());
}

You could do this manually in order to gain more control over the enumeration than provided by foreach. In the Points of Interest section we have a quick look at the IL code and we can see directly that foreach is turned into MoveNext() and Current as shown above.

To Understand Enumeration we Must First Count to Ten

Implements IEnumerable

So, asuming we understand what a foreach loop actually does then it gets even easier to understand the 'old' or classic way of making an object enumerable. We start with the class we want to enumerate.

public class ByEnumerator : IEnumerable<double>
{
    Public IEnumerator<Double> GetEnumerator()
    {
        Return New MyNewEnumerator()
    }
 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}  

It really is that straightforward. IEnumerable simply demands implementation of those two methods and you just point the less specific one at the other.

In my case I was enumerating a two dimensional array of double and wanted to be able to choose between two different IEnumerators. So we need a few changes. We've done the following:

Added a constructor that simply takes an existing 2 dimensional array of double.

public ByEnumerator(double[,] matrix)
{
    this._matrix = matrix;
    this._matrixEnumerator = MatrixEnumerator.Horizontal;
}

A property to allow the choice between different enumeration methods and an enum to represent the different possible enumerators.

public MatrixEnumerator Enumerator
{ 
    get { return this._matrixEnumerator; }
    set { this._matrixEnumerator = value; }
}
 
public enum MatrixEnumerator
{
    Vertical,
    Horizontal
}

And finally some conditional code to create the desired IEnumerator instance.

public IEnumerator<double> GetEnumerator()
{
    switch (this._matrixEnumerator)
    {
        case MatrixEnumerator.Vertical:
            return new VerticalMatrixEnumerator(this._matrix);
        case MatrixEnumerator.Horizontal:
            return new HorizontalMatrixEnumerator(this._matrix);
        default:
            throw new InvalidOperationException();
    }
}
 
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

Now all we need to do is implement those two new classes, the Horizontal and Vertical enumerators.

Implements IEnumerator

You can see that the IEnumerator we return depends on the class property, Enumerator. Now we just need to create two two IEnumerator classes mentioned in the code above. We'll show the Horizontal one first and you'll see that we simply add code to the four methods mentioned earlier. There are various private fields to keep track of where we are in the array and other than that the important method is MoveNext(). You can also see that we pass a copy of the 2D array from the IEnumerable class to the IEnumerator class (I assume this is why modifiying an object whilst enumerating it tends to cause chaos).

First off the private fields and the constructor.

public class HorizontalMatrixEnumerator : IEnumerator<double>
{
    private double[,] _matrix;
    private int _colIndex;
    private int _rowIndex;
    private double _curItem;
    private int _lastCol;
    private int _lastRow;
 
    public HorizontalMatrixEnumerator(double[,] matrix)
    {
        this._matrix = matrix;
        this._colIndex = -1;
        this._rowIndex = 0;
        this._curItem = double.NaN;
        this._lastCol = matrix.GetUpperBound(1);
        this._lastRow = matrix.GetUpperBound(0);
    }
  • The _matrix field is pretty obvious, it's a copy of the 2d array of double sent by the IEnumerator object (I am deliberately avoiding discussion of shallow / deep copies and values / references - keep the focus)
  • The _colIndex and _rowIndex fields keep track of where we are in the array, our position. We'll explain why they are initialised as 0 and -1 in a minute.
  • _curItem is the field behind the Current property.
  • _lastCol and _lastRow just provide convenient access to the UpperBounds of the array.

Now the Current property.

    public double Current
    {
        get
        {
            if (double.IsNaN(this._curItem))
            {
                throw new InvalidOperationException();
            }
            return this._curItem;
        }
    }
 
    object System.Collections.IEnumerator.Current
    {
        get { return this.Current(); }
    }

As noted, it's just a wrapper for the _curItem field. It throws an exception if it's NaN - it is initialised as such in the constructor - this ensures that we throw an exception if the user of the object (the foreach loop for example) tries to use it before MoveNext() has been called, it's a bit nicer than letting them find out when their code throws a wobbly because it was expecting a useful value and got something it didn't know how to deal with.

Now the interesting bit, MoveNext().

    public bool MoveNext()
    {
    if (this._colIndex == this._lastCol & this._rowIndex == this._lastRow)
        {
            return false;
        }
        if (this._colIndex == this._lastCol)
        {
            this._colIndex = 0;
            this._rowIndex += 1;
        }
        else
        {
            this._colIndex += 1;
        }
        this._curItem = this._matrix[this._rowIndex, this._colIndex];
        return true;
    }
  • First off we check we are not already at the end of the array, if we are then we've already finished and we return false, allowing the while {true} do ... loop from earlier to exit gracefully
  • Then we check if we are at the last column in a row, if we are we reset the column index to 0 and move to the next row
  • If we weren't at the end of a row then we simply move to the next column in that row
  • In the latter two cases we return true, telling the while {true} do ... loop that there's at least one more new value to loop over
  • Now the explanation I promised earlier, why do we initialise _rowIndex as 0 and _colIndex as -1? Well, MoveNext() gets called before Current, so if we set both to 0 then the code above would immedaitely move us to the second column in the first row (0,1) and we would never set the value of Current to be the array value at (0,0)

It's worth noting that the MoveNext() method presented above is essentially a pair of nested for loops which have been made unnecessarily complex, the yield version (that we will show shortly) is easier to code and is also easier to understand. The image below should make that clear.

Traversing the array horizontally

You can see that every time the column index reaches 2 we need to reset it to 0 and add 1 to the row index, or simply add 1 to the column index until is does reach 2.

Finally the Reset() method just puts the relevant fields back to how they were when the object was created.

    public void Reset()
    {
        this._colIndex = -1;
        this._rowIndex = 0;
        this._curItem = double.NaN;
    }
} // Class

The Vertical enumerator changes a few things but mostly the MoveNext() method as shown below. We just flip around the row and column variables in the if () ... else ... section such that we move down columns rather than across rows. We also initialise _rowIndex to -1 (instead of 0) and _colIndex to 0 (instead of -1).

    public bool MoveNext()
    {
        if (this._colIndex == this._lastCol & this._rowIndex == this._lastRow)
        {
            return false;
        }
        if (this._rowIndex == this._lastRow)
        {
            this._rowIndex = 0;
            this._colIndex += 1;
        }
        else
        {
            this._rowIndex += 1;
        }
        this._curItem = this._matrix[this._rowIndex, this._colIndex];
        return true;
    }

Now whilst that's pretty easy, it means creating two extra classes and (relatively) more code than feels necessary for something relatively simple, can we do better? Let's have a crack at yield...

Who Needs IEnumerators?

Yield to temptation. It may not pass your way again

So how do we do the same thing with yield? Most of the code doesn't change. The first thing is that the GetEnumerator() method doesn't create new objects, it calls a couple of private class methods. Other than that it's identical, you have to be watching carefully just to notice that something has changed.

public IEnumerator<double> GetEnumerator()
{
    switch (this._matrixEnumerator)
    {
        case MatrixEnumerator.Horizontal:
            return this.HorizontalEnumerator();
        case MatrixEnumerator.Vertical:
            return this.VerticalEnumerator();
        default:
            throw new InvalidOperationException();
    }
}

Essentially identical, but rather than return new HorizontalMatrixEnumerator(this._matrix) we see return this.HorizontalEnumerator(), which is a method reference not an object. Then we just need to implement those two new methods.

private IEnumerator<double> VerticalEnumerator()
{
    if (this._matrix != null)
    {
        for (int col = 0; col <= this._matrix.GetUpperBound(1); col++)
        {
            for (int row = 0; row <= this._matrix.GetUpperBound(0); row++)
            {
                yield return this._matrix[row, col];
            }
        }
    } else {
          throw new InvalidOperationException();
    }
}
 
private IEnumerator<double> HorizontalEnumerator()
{
    if (this._matrix != null)
    {
        for (int row = 0; row <= this._matrix.GetUpperBound(0); row++)
        {
            for (int col = 0; col <= this._matrix.GetUpperBound(1); col++)
            {
                yield return this._matrix[row, col];
            }
        }
    } else {
        throw new InvalidOperationException();
    }
}

It took me a while to figure out exactly how to declare those two methods but eventually you work out that they need to have a return type of IEnumerator. You can see this in the code and other than that it involves no more than looping your way through the array. Not much easier technically, in fact no easier at all but certainly less code and without those extra classes to maintain.

What is very clear though is that it's a lot less code than two IEnumerator classes. It's also a lot easier to see what's going on, it is clearly two nested for loops, which as we said above is what the MoveNext() method is actually doing, only in a more complex way.

By way of explanation this really isn't very much, but if you've read and understood how to do the same thing with IEnumerators then understanding yield is as simple as this:

  1. Recognising that the code looks really very similar to the MoveNext() methods (in essence), only it's simpler
  2. Realising that when you use an Iterator method with yield it doesn't execute to its natural conclusion, every time it encounters a yield it does the same as a return statement and then stops right where it is, the method doesn't finish, then next time you call the method it picks up where it left off, i.e. it does not start afresh each time you call it. So during a foreach loop this method will run from start to finish once only, not once for each value in the array. It's a method that returns values many times not once - magic (or perhaps not magic, just the compiler saving us time since behind the scenes(^) it apparently creates IEnumerator like code anyway...)

The only difference between the two is which way you loop through the array, columns first or rows first.

State Machines

If this explanation doesn't work for you there's of options out there that explain it in different ways and to greater detail, Google(^) is your friend.

OK, I'm not going to try and explain this in great detail (the article is aimed at beginners and there's much I don't yet understand or at least I imagine there is), just enough (I hope) to make sense of what yield actually does and how. You can almost think of the Iterator method as a separate programme, the programme running the foreach loop on your IEnumerable object then sends a message to the other one saying 'give me the next one' - right now this sounds exactly like a MoveNext() followed by a Current - but the clever bit is the other programme stops execution as soon as it has executed yield statement, then waits until you ask it for the next one, at which point it executes the next statement and continues until it reachs the next yield statement, executes it and then waits once more. In the case above each time it reaches the 'next' yield statement it is actually the same one, but in a loop, you could equally have a set of yield statements one after the other (see the MSDN example(^))

Points of Interest

Did you learn anything interesting / fun / annoying?

Yes, some donkey (when designing .Net and VB / C#) decided that it would be smart to index arrays as (row, column) and DataGridViews as (column, row).

Did you do anything particularly clever or wild or zany?

Wild or zany? This is what the CP article template suggests. Really? When coding? Wild and zany. Maybe if I drank too much coffee and did something really crazy that was in no way related to coding but otherwise, no.

foreach == while (MoveNext()) + Current

We can show clearly the point made earlier, that running foreach on an IEnumerable object is the same as manually creating the IEnumerator and initiating a while (true) do ... loop. This happens to be the code for the foreach performed on the object which uses an IEnumerator rather than yield, although it's very similar indeed for the yield version.

IL_016d: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<float64> IEnumerableCS.ByEnumerator::GetEnumerator()
IL_0172: stloc.s CS$5$0001
.try
{
    IL_0174: br.s IL_01a1
    // loop start (head: IL_01a1)
        IL_0176: ldloc.s CS$5$0001
        IL_0178: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<float64>::get_Current()
        IL_017d: conv.r8
        IL_017e: stloc.s Val
        IL_0180: nop
        IL_0181: ldloc.s output
        IL_0183: ldloc.s Val
        IL_0185: ldc.i4.2
        IL_0186: call float64 [mscorlib]System.Math::Round(float64, int32)
        IL_018b: stloc.s CS$0$0002
        IL_018d: ldloca.s CS$0$0002
        IL_018f: call instance string [mscorlib]System.Double::ToString()
        IL_0194: ldstr " ¦ "
        IL_0199: call string [mscorlib]System.String::Concat(string, string, string)
        IL_019e: stloc.s output
        IL_01a0: nop

        IL_01a1: ldloc.s CS$5$0001
        IL_01a3: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        IL_01a8: stloc.s CS$4$0000
        IL_01aa: ldloc.s CS$4$0000
        IL_01ac: brtrue.s IL_0176
    // end loop

    IL_01ae: leave.s IL_01c4
} // end .try
finally
{
    IL_01b0: ldloc.s CS$5$0001
    IL_01b2: ldnull
    IL_01b3: ceq
    IL_01b5: stloc.s CS$4$0000
    IL_01b7: ldloc.s CS$4$0000
    IL_01b9: brtrue.s IL_01c3

    IL_01bb: ldloc.s CS$5$0001
    IL_01bd: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    IL_01c2: nop

    IL_01c3: endfinally
// end handler

You can clearly see that at IL_0174 it jumps (unconditionally) to the short section that includes the call to MoveNext(), starting at IL_01a1. At the end of that section it branches on true (IL_01b9) to the slightly longer section above (assuming MoveNext() returned true), starting at IL_0176 which then calls Current. Completely consistent with a while (true) do ... loop, i.e. if the object to enumerate is empty it never tries to call Current; meanwhile for a non-empty object the first time that MoveNext() returns false is when it tries to move past the last position, at which point the branch on true (IL_01b9) doesn't branch and the code flows onwards out of the loop.

How Does the Compiler Implement Yield?

I thought I'd remembered reading that when you use yield the compiler responds by creating an IEnumerator, so it's really just shorthand to allow for code that's easier to read, write and maintain, I thought I'd see if this was true - since I have the perfect examnple to test it on - by looking at what IL code is produced by the compiler. (Try ILSpy(^) or the ILDisassembler that comes with Windows 7.1 SDK(^).) Here's what you see, first we'll look at the classes and code that is produced, in particular the ByYield class.

The ByYield structure by ILSpy

There's our HorizontalEnumerator method highlighted, now what does the IL code say?

.method private hidebysig 
	instance class [mscorlib]System.Collections.Generic.IEnumerator`1<float64> HorizontalEnumerator () cil managed 
{
	// Method begins at RVA 0x22ec
	// Code size 16 (0x10)
	.maxstack 2
	.locals init (
		[0] class IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'
	)

	IL_0000: ldc.i4.0
	IL_0001: newobj instance void IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::.ctor(int32)
	IL_0006: stloc.0
	IL_0007: ldloc.0
	IL_0008: ldarg.0
	IL_0009: stfld class IEnumerableCS.ByYield IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>4__this'
	IL_000e: ldloc.0
	IL_000f: ret
} // end of method ByYield::HorizontalEnumerator

I don't get all of that, but I do see a reference to IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4. We can see that in the image just above - it's the third item in the ByYield description, just under DerivedTypes. Here's what's contained in that class member.

The ByYield structure by ILSpy

There's are old friends, two methods MoveNext() and Reset(), and the Current property; those methods were not written in the ByYield class, the compiler has created them. Cearly the compiler treats yield as shorthand for 'please write me an IEnumerator'.

We can compare, at a very amateur level, whether the compilers version of an IEnumerator is much better than its attempt to optimise the MoveNext() we wrote above. The compilers conversion of the two nested for loops with a yield in the middle is on the left. It's conversion of my MoveNext() method is on the right.

.method private final hidebysig newslot virtual 
	instance bool MoveNext () cil managed 
{
	.override method instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
	// Method begins at RVA 0x225c
	// Code size 248 (0xf8)
	.maxstack 4
	.locals init (
		[0] bool CS$1$0000,
		[1] int32 CS$4$0001,
		[2] bool CS$4$0002
	)

	IL_0000: ldarg.0
	IL_0001: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>1__state'
	IL_0006: stloc.1
	IL_0007: ldloc.1
	IL_0008: switch (IL_0019, IL_0017)

	IL_0015: br.s IL_001b

	IL_0017: br.s IL_007f

	IL_0019: br.s IL_0020

	IL_001b: br IL_00f2

	IL_0020: ldarg.0
	IL_0021: ldc.i4.m1
	IL_0022: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>1__state'
	IL_0027: nop
	IL_0028: ldarg.0
	IL_0029: ldfld class IEnumerableCS.ByYield IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>4__this'
	IL_002e: ldfld float64[0..., 0...] IEnumerableCS.ByYield::_matrix
	IL_0033: ldnull
	IL_0034: ceq
	IL_0036: stloc.2
	IL_0037: ldloc.2
	IL_0038: brtrue IL_00ea

	IL_003d: nop
	IL_003e: ldarg.0
	IL_003f: ldc.i4.0
	IL_0040: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<row>5__5'
	IL_0045: br.s IL_00c4

	IL_0047: nop
	IL_0048: ldarg.0
	IL_0049: ldc.i4.0
	IL_004a: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<col>5__6'
	IL_004f: br.s IL_0095

	IL_0051: nop
	IL_0052: ldarg.0
	IL_0053: ldarg.0
	IL_0054: ldfld class IEnumerableCS.ByYield IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>4__this'
	IL_0059: ldfld float64[0..., 0...] IEnumerableCS.ByYield::_matrix
	IL_005e: ldarg.0
	IL_005f: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<row>5__5'
	IL_0064: ldarg.0
	IL_0065: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<col>5__6'
	IL_006a: call instance float64 float64[0..., 0...]::Get(int32, int32)
	IL_006f: stfld float64 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>2__current'
	IL_0074: ldarg.0
	IL_0075: ldc.i4.1
	IL_0076: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>1__state'
	IL_007b: ldc.i4.1
	IL_007c: stloc.0
	IL_007d: br.s IL_00f6

	IL_007f: ldarg.0
	IL_0080: ldc.i4.m1
	IL_0081: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>1__state'
	IL_0086: nop
	IL_0087: ldarg.0
	IL_0088: dup
	IL_0089: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<col>5__6'
	IL_008e: ldc.i4.1
	IL_008f: add
	IL_0090: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<col>5__6'

	IL_0095: ldarg.0
	IL_0096: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<col>5__6'
	IL_009b: ldarg.0
	IL_009c: ldfld class IEnumerableCS.ByYield IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>4__this'
	IL_00a1: ldfld float64[0..., 0...] IEnumerableCS.ByYield::_matrix
	IL_00a6: ldc.i4.1
	IL_00a7: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32)
	IL_00ac: cgt
	IL_00ae: ldc.i4.0
	IL_00af: ceq
	IL_00b1: stloc.2
	IL_00b2: ldloc.2
	IL_00b3: brtrue.s IL_0051

	IL_00b5: nop
	IL_00b6: ldarg.0
	IL_00b7: dup
	IL_00b8: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<row>5__5'
	IL_00bd: ldc.i4.1
	IL_00be: add
	IL_00bf: stfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<row>5__5'

	IL_00c4: ldarg.0
	IL_00c5: ldfld int32 IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<row>5__5'
	IL_00ca: ldarg.0
	IL_00cb: ldfld class IEnumerableCS.ByYield IEnumerableCS.ByYield/'<HorizontalEnumerator>d__4'::'<>4__this'
	IL_00d0: ldfld float64[0..., 0...] IEnumerableCS.ByYield::_matrix
	IL_00d5: ldc.i4.0
	IL_00d6: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32)
	IL_00db: cgt
	IL_00dd: ldc.i4.0
	IL_00de: ceq
	IL_00e0: stloc.2
	IL_00e1: ldloc.2
	IL_00e2: brtrue IL_0047

	IL_00e7: nop
	IL_00e8: br.s IL_00f1

	IL_00ea: nop
	IL_00eb: newobj instance void [mscorlib]System.InvalidOperationException::.ctor()
	IL_00f0: throw

	IL_00f1: nop

	IL_00f2: ldc.i4.0
	IL_00f3: stloc.0
	IL_00f4: br.s IL_00f6

	IL_00f6: ldloc.0
	IL_00f7: ret
} // end of method '<HorizontalEnumerator>d__4'::MoveNext
.method public final hidebysig newslot virtual 
	instance bool MoveNext () cil managed 
{
	// Method begins at RVA 0x251c
	// Code size 139 (0x8b)
	.maxstack 4
	.locals init (
		[0] bool CS$1$0000,
		[1] bool CS$4$0001
	)

	IL_0000: nop
	IL_0001: ldarg.0
	IL_0002: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_colIndex
	IL_0007: ldarg.0
	IL_0008: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_lastCol
	IL_000d: ceq
	IL_000f: ldarg.0
	IL_0010: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_rowIndex
	IL_0015: ldarg.0
	IL_0016: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_lastRow
	IL_001b: ceq
	IL_001d: and
	IL_001e: ldc.i4.0
	IL_001f: ceq
	IL_0021: stloc.1
	IL_0022: ldloc.1
	IL_0023: brtrue.s IL_002a

	IL_0025: nop
	IL_0026: ldc.i4.0
	IL_0027: stloc.0
	IL_0028: br.s IL_0089

	IL_002a: ldarg.0
	IL_002b: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_colIndex
	IL_0030: ldarg.0
	IL_0031: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_lastCol
	IL_0036: ceq
	IL_0038: ldc.i4.0
	IL_0039: ceq
	IL_003b: stloc.1
	IL_003c: ldloc.1
	IL_003d: brtrue.s IL_0058

	IL_003f: nop
	IL_0040: ldarg.0
	IL_0041: ldc.i4.0
	IL_0042: stfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_colIndex
	IL_0047: ldarg.0
	IL_0048: dup
	IL_0049: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_rowIndex
	IL_004e: ldc.i4.1
	IL_004f: add
	IL_0050: stfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_rowIndex
	IL_0055: nop
	IL_0056: br.s IL_0068

	IL_0058: nop
	IL_0059: ldarg.0
	IL_005a: dup
	IL_005b: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_colIndex
	IL_0060: ldc.i4.1
	IL_0061: add
	IL_0062: stfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_colIndex
	IL_0067: nop

	IL_0068: ldarg.0
	IL_0069: ldarg.0
	IL_006a: ldfld float64[0..., 0...] IEnumerableCS.HorizontalMatrixEnumerator::_matrix
	IL_006f: ldarg.0
	IL_0070: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_rowIndex
	IL_0075: ldarg.0
	IL_0076: ldfld int32 IEnumerableCS.HorizontalMatrixEnumerator::_colIndex
	IL_007b: call instance float64 float64[0..., 0...]::Get(int32, int32)
	IL_0080: stfld float64 IEnumerableCS.HorizontalMatrixEnumerator::_curItem
	IL_0085: ldc.i4.1
	IL_0086: stloc.0
	IL_0087: br.s IL_0089

	IL_0089: ldloc.0
	IL_008a: ret
} // end of method HorizontalMatrixEnumerator::MoveNext

I'm not qualified to comment with any professionalism, but whilst the compiler clearly has to do more work with yield in order to turn two nested for loops into an IEnumerator than when it is given dedicated IEnumerator code, there isn't (seemingly) a huge difference between the two. Whether one of them is more efficient I don't know - maybe someone has time to test... I guess it shouldn't be a surprise, the fact that the compiler is capable of doing this at all impresses me, especially if you imagine just how complex some methods with a yield statement might be and it has to be able to handle tham all.

It's probably worth thinking about this is if you have a class that may contain very large amounts of data, which needs to be enumerable and where time is critical, in such cases trying both your own IEnumerator and yield is probably worth the time, to see which results in quicker execution.

History

Version 5 (21 April 2013): Fixed issue with test for var == double.NaN, should have been double.IsNaN(var); added section on 'foreach == while (MoveNext()) + Current' further typographical errors fixed and re-wrote some sections for clarity

Version 4 (13 April 2013): [No code changes in demo app] Re-wrote introduction to improve flow, fixed some typographical errors

Version 3 (12 April 2013): [No code changes in demo app] Changed do while to while do

Version 2 (11 April 2013): [No code changes in demo app] Clarifications and extra information subsequent to reader comments and addition of new section, 'How Does the Compiler Implement Yield?'

Version 1 (7 April 2013): Shiny and Newny and New

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mike-MadBadger
Engineer
France France
A hobbyist begin-again-er!
 
Spent a few years longer ago than I care to remember with BBC Basic, a couple of years with Delphi about 10-15 years ago with a smattering af MS Access applications along the way. Dropped out of it completely except for the occasional Excel macro.
 
Finally picked up the baton again with VB.Net in VS2010 and now VS 2012and have been playing around quite a bit with a few odds and sodds, learning much as I go - and long may it continue.
 
I don't work even close to the IT industry and probably never will, but I do enjoy it as a hobby.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberJaime Olivares15-Apr-13 6:24 
GeneralRe: My vote of 5 PinmemberMike-MadBadger15-Apr-13 10:43 
QuestionA thing that is simple wrong. PinmemberPaulo Zemek13-Apr-13 4:30 
AnswerRe: A thing that is simple wrong. [modified] PinmemberAndreas Gieriet14-Apr-13 4:34 
AnswerRe: A thing that is simple wrong. [modified] PinmemberMike-MadBadger14-Apr-13 20:17 
GeneralMy vote of 4 PinmemberPaulo Zemek13-Apr-13 4:25 
GeneralMy vote of 4 PinmemberShubham Choudhary11-Apr-13 21:13 
gooooooooooood
GeneralRe: My vote of 4 PinmemberMike-MadBadger12-Apr-13 10:16 
QuestionNice article Pinmemberjfriedman11-Apr-13 17:36 
AnswerRe: Nice article PinmemberMike-MadBadger11-Apr-13 20:55 
GeneralRe: Nice article Pinmemberjfriedman11-Apr-13 21:22 
GeneralRe: Nice article PinmemberMike-MadBadger12-Apr-13 10:18 
GeneralRe: Nice article Pinmemberjfriedman12-Apr-13 15:36 
AnswerRe: Nice article PinmemberAndreas Gieriet14-Apr-13 6:06 
GeneralRe: Nice article Pinmemberjfriedman14-Apr-13 6:47 
GeneralRe: Nice article PinmemberAndreas Gieriet14-Apr-13 8:34 
GeneralRe: Nice article Pinmemberjfriedman14-Apr-13 8:58 
GeneralRe: Nice article Pinprofessionaljfriedman15-Apr-13 11:22 
QuestionWeird introduction, misleading descriptions, sloppy editing PinmemberAndreas Gieriet11-Apr-13 14:08 
AnswerRe: Weird introduction, misleading descriptions, sloppy editing PinmemberMike-MadBadger11-Apr-13 20:53 
GeneralRe: Weird introduction, misleading descriptions, sloppy editing [modified] PinmemberAndreas Gieriet12-Apr-13 1:00 
GeneralRe: Weird introduction, misleading descriptions, sloppy editing PinmemberMike-MadBadger12-Apr-13 10:15 
GeneralRe: Weird introduction, misleading descriptions, sloppy editing [modified] PinmemberAndreas Gieriet14-Apr-13 4:21 
GeneralRe: Weird introduction, misleading descriptions, sloppy editing [modified] PinmemberMike-MadBadger14-Apr-13 20:38 
GeneralRe: Weird introduction, misleading descriptions, sloppy editing PinmemberAndreas Gieriet15-Apr-13 7:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 21 Apr 2013
Article Copyright 2013 by Mike-MadBadger
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid