Click here to Skip to main content
15,886,689 members
Articles / Programming Languages / C#
Article

Using IEnumerator and IEnumerable in the .NET Framework

Rate me:
Please Sign up or sign in to vote.
4.62/5 (26 votes)
3 Dec 20036 min read 332.8K   1.8K   81   17
An article on the .NET Framework's implementation of the Iterator pattern

Introduction

This article discusses the IEnumerator and IEnumerable interfaces in the .NET framework. Enumerators can be an incredibly powerful way of iterating through data. I had noticed that the MSDN library didn't contain many examples on the use of enumerators, and I felt that they are a too useful and powerful construction to have, without sufficient examples on their use.

Background

I first came across the concept of iterators from another object oriented language called Magik. In that language it is possible to create an iter method which can be used in for loops in a similar way to the C# foreach. I wanted to be able to recreate some of the very powerful iterators I had used in Magik in C#.

Iterators are useful in a variety of situations, e.g. when you want to traverse a list in different ways, to be able to traverse the contents of an object without exposing the internal representation, or to provide a uniform way of traversing different aggregate structures.

Iterators are also very useful in situations where the amount of data is not easily known at the start of the process. Or where the data is coming from an outside, possibly asynchronous source - In this situation the use of an iterator can make, the method using the iterator, considerably easier to read than, trying to code it into the method itself, as all the logic for accessing the data is in the iterator.

A further common usage that I have come across is the deserialisation of a file. The iterator reads the file, calls the necessary factory methods and passes back objects that it have been constructed based on data in the file.

Using the code

In the sample code, I have presented a small problem, enumerating through the cards in a standard deck of cards, with the Jokers removed and shown two ways in which an enumerator can be used in the .NET Framework. First, using a while loop accessing the IEnumerator based object directly, and second, using a foreach loop accessing the enumerator through the IEnumerable interface.

This first enumerator class, suitsEnumerator, will return each of the suits in a deck of cards.

First notice that the class uses the IEnumerator interface. This is the base interface for all enumerators. Also note that to use this interface a reference to System.Collections needs to be included.

C#
using System.Collections;

public class suitsEnumerator : IEnumerator
{

The following code sets up the class. As this is a very simple enumeration, I have placed all the values to be enumerated in a simple array. I have also initialised m_nCurr to the index of the current element. As enumerators are always initially pointed to just before the first element, this index is set to -1. The Reset() method also sets the current index back to -1.

C#
private static string[] suits = {"Hearts","Spades","Diamonds","Clubs"};
private int m_nCurr = -1;

public suitsEnumerator() {}

public void Reset()
{
    m_nCurr = -1;
}

The MoveNext() method, as the name implies, moves the enumerator on to the next element. The return value indicates whether there is more data or not, and only when the thing being iterated over is exhausted will it return false.

In this example the method only updates the index, but in other implementations, the enumerator class may have a member variable to hold the current value if the lookup is complex.

C#
public bool MoveNext()
{
    m_nCurr++;
    if (m_nCurr >= 4)
        return false;
    return true;
}

Finally, there is the Current property, which returns the current element. In this simple implementation a check is done to ensure the index is valid and the element at that index of the array is returned. If the index is invalid then an exception is raised.

As Current does not move the enumerator to the next element, it will always return the same value until either MoveNext() or Reset() is called.

C#
public object Current
    {
        get
        {
            if (m_nCurr<0)
                throw new InvalidOperationException();
            if (m_nCurr>3)
                throw new InvalidOperationException();
            return suits[m_nCurr];
        }
    }
}

This is a very simple enumerator that can be used quite easily like the example below:

C#
Console.WriteLine("The suits are:");
IEnumerator suits = new suitsEnumerator();
while(suits.MoveNext())
    Console.WriteLine(suits.Current);

In the provided code, there is another similar IEnumerator derived class called cardValuesEnumerator that contains each possible value that a card may contain.

The second part of using enumerators in the .NET Framework is the IEnumerable interface. It provides the method GetEnumerator() with an IEnumerator as a return value.Also the IEnumerable interface can be found in the System.Collections namespace.

C#
public class deckOfCards : IEnumerable
{
    ...
    public IEnumerator GetEnumerator()
    {
        return new deckOfCardsEnumerator();
    }
    ...
}

The IEnumerable interface is used on classes that are to be used with the foreach statement. The enumerator returned from the GetEnumerator() method is used by the foreach statement.

C#
Console.WriteLine("The deck of cards contains:");
foreach(object card in new deckOfCards())
    Console.WriteLine(card);

Normally the class inheriting from IEnumerable will not be the same class that inherits from the IEnumerator interface. It can do, however if it is then the class will not be thread safe, nor can you nest two or more iterations of the elements of the same class within each other. In otherwords the following will not be possible:

C#
foreach(object obj1 in MyCollection)
{
    foreach(object obj2 in MyCollection)
    {
        // Do something
    }
}

If you can assert that there will only ever be one thread and never any nesting then the the collection class with the IEnumerator interface can also have the IEnumerable interface with the GetEnumerator() method returning this.

Exposing More Than One Enumerator

If you have a collection that could expose many ways of iterating through its contents you may like to create a number of IEnumerator classes. If you do then the GetEnumerator() on the collection class may become redundant unless you plan to return some default enumerator. In this case a class with the IEnumerable and IEnumerator interfaces can be created for each type of iteration so that the return value from the original collection class can be dropped directly into a foreach statement.

The following code snipped shows an example of a class exposing many enumerators that can be easily used in a foreach statement:

C#
class SuperCollection
{
	// All the ususal collection collection methods go here
	public ForwardEnumeration InAscendingOrder
	{
		get
		{
			return new ForwardEnumeration(/*some args*/);
		}
	}
	
	public ReverseEnumeration InDescendingOrder
	{
		get
		{
			return new ReverseEnumeration(/*some args*/);
		}
	}
}

class ForwardEnumeration : IEnumerator, IEnumerable
{
	public ForwardEnumeration(/*some args*/)
	{
		// Constructor Logic Here
	}

	// From the IEnumerable Interface
	public IEnumerator GetEnumerator
	{
		return this;
	}

	/* Put IEnumerator logic here*/
}

class ReverseEnumeration : IEnumerator, IEnumerable
{
	public ReverseEnumeration(/*some args*/)
	{
		// Constructor logic here
	}
	
	// From the IEnumerable Interface
	public IEnumerator GetEnumerator
	{
		return this;
	}
	
	/* Put IEnumerator logic here*/
}

/*Using the above classes*/
public void SomeMethod(SuperCollection manyThings)
{
	foreach(object item in manyThings.InAscendingOrder)
	{
		// Do something with the each object
	}
	
	foreach(object item in manyThings.InDescendingOrder)
	{
		// Do something with each object
	}
}

In many situations this may produce a cleaner construction than implementing it as two classes. e.g. Where the IEnumerable based class only serves to construct and return the IEnumerator based class.

Personally, I would have also liked the foreach to permit the direct use of a IEnumerator based class, but if tried then the compiler will issue the error:

"foreach statement cannot operate on variables of 
type '<class name>' because '<class name>' 
does not contain a definition for 
'GetEnumerator', or it is inaccessible."

Points of Interest

The biggest difference between the C# implementation, which is essentially a form of the iterator pattern as found in book mentioned below, and the Magik implementation is that, in Magik a method can be attributed as an iter method, whereas in C# the enumerator is a different class. In C# this can pose additional challenges, for one the iterator may need to have access to the internal structure of the thing being enumerated.

If you want to read more about iterators, and other design patterns, I recommend the book mentioned below. It is an excellent reference of design patterns and also won a Software Development Magazine Productivity Award.

If you have any questions please feel free to contact me.

Bibliography

Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, USA, 1994

History

  • Created article: 2-May-2003
  • Updated: 4-May-2003
  • Updated: 3-December-2003

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Technical Lead
Scotland Scotland
Have been a Code Project MVP 5 years running and was Microsoft C# MVP 4 years running, MBCS, MIAP and a whole bunch of other stuff. Now I just help run Scottish Developers which is a user group with software development events in Edinburgh, Glasgow and Dundee and I have also started an open source project to help with Password Validation

Main topics I blog about:
* Parallelization in .NET
* Code Quality
* Data Security

Comments and Discussions

 
SuggestionThank you Pin
AT--o22-Sep-13 12:20
AT--o22-Sep-13 12:20 
GeneralMy vote of 5 Pin
CS140112-Feb-12 19:22
CS140112-Feb-12 19:22 
GeneralMore information on Enumerators Pin
Colin Angus Mackay11-Sep-04 23:32
Colin Angus Mackay11-Sep-04 23:32 
GeneralProblem While calling for each from visual basic Pin
Ami Shah23-Aug-04 3:03
Ami Shah23-Aug-04 3:03 
GeneralRe: Problem While calling for each from visual basic Pin
Colin Angus Mackay23-Aug-04 3:20
Colin Angus Mackay23-Aug-04 3:20 
GeneralPerformance Pin
Thomas Freudenberg4-May-03 2:03
Thomas Freudenberg4-May-03 2:03 
Don Box has written some tips[^] to improve the performance of enumerators.

Additionally, Paul Kimmel has pointed out[^], that the foreach statement uses the IEnumerator explicitely. I think that's worth to be mentioned in your article.

Anyway, thanks for your article.

Regards
Thomas


Disclaimer:
Because of heavy processing requirements, we are currently using some of your unused brain capacity for backup processing. Please ignore any hallucinations, voices or unusual dreams you may experience. Please avoid concentration-intensive tasks until further notice. Thank you.

GeneralRe: Performance Pin
Thomas Freudenberg4-May-03 2:15
Thomas Freudenberg4-May-03 2:15 
GeneralRe: Performance Pin
Colin Angus Mackay4-May-03 10:58
Colin Angus Mackay4-May-03 10:58 
GeneralRe: Performance Pin
Thomas Freudenberg4-May-03 11:20
Thomas Freudenberg4-May-03 11:20 
GeneralRe: Performance Pin
peterchen12-Dec-03 2:51
peterchen12-Dec-03 2:51 
GeneralRe: Performance Pin
Colin Angus Mackay12-Dec-03 10:20
Colin Angus Mackay12-Dec-03 10:20 
GeneralRe: Performance Pin
Kindo Malay11-May-04 22:10
Kindo Malay11-May-04 22:10 
GeneralRe: Performance Pin
Thomas Freudenberg11-May-04 23:05
Thomas Freudenberg11-May-04 23:05 
GeneralRe: Performance Pin
Colin Angus Mackay11-May-04 23:12
Colin Angus Mackay11-May-04 23:12 
GeneralRe: Performance Pin
Thomas Freudenberg11-May-04 23:13
Thomas Freudenberg11-May-04 23:13 
GeneralRe: Performance Pin
Colin Angus Mackay11-May-04 23:11
Colin Angus Mackay11-May-04 23:11 
GeneralRe: Performance Pin
Kindo Malay12-May-04 11:47
Kindo Malay12-May-04 11:47 

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

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