The LINQ Principle
Written by Mike James   
Thursday, 25 June 2020
Article Index
The LINQ Principle
IEnumerable
Generic enumeration
Raw LINQ
More with LINQ

Banner

IEnumerable

The reason is that any class offering an enumerator has to implement the IEnumerable interface. This has just a single method GetEnumerator which returns the instance of the class that provides the methods of the IEnumerator.

If you think about it for a moment it is obvious that IEnumerable has to be implemented by the data collection class that holds the items to be enumerated so we have to add it to the class definition:

class TestCollection:
IEnumerator,IEnumerable
{

The single method that we have to implement is trivial as the instance of the TestCollection class concerned provides its own enumerators, i.e. it is the enumerator to be returned:

 IEnumerator IEnumerable.GetEnumerator()
{
return  this;
}
}

Now we can write some code that makes use of our data collection with enumeration. The simplest thing to try out is a foreach loop:

TestCollection col = 
new TestCollection(5);
foreach (object o in col)
{
MessageBox.Show(o.ToString());
}

Notice that the iterator o has to be defined as an object. We can use an int in the foreach loop as it supports implicit casting.

For example:

foreach (int o in col)
{
MessageBox.Show(o.ToString());
}

works perfectly unless you happen to return something that can’t be cast to an int at runtime with the result that an exception is raised. If you want to do better then you need to change the enumeration interfaces to their generic forms – which is what we have to do anyway to use LINQ.

Separating the enumerator

Before moving on it is worth commenting on why we usually implement the enumerator as a separate class. Consider what happens if we try to use the current enumerator in a nested foreach loop?

The same enumerator would be returned by GetEnumerator and hence the nested loops would interfere with each other. 

If you want to write nested loops or allow multiple enumerations to happen concurrently you need to implement the enumerator as a separate class and you need to create an instance of that class each time an enumerator is called for by GetEnumerator.

As nesting of queries is very common we need to do the job properly before moving on to consider LINQ.

All we need to do is separate out the enumeration methods into a new class. This class has to keep track of where it is in the enumeration and it needs to keep track of which instance of the collection it is enumerating:

class TestCollectionEnnumerator:IEnumerator
{
private TestCollection m_Instance;
private int loc = -1;

If you create the enumeration class as an inner class of the data collection, i.e. TestCollection, it will have access to all of the data collection’s variables and methods and this makes it easier for the two classes to work together.

The only new method we need is a constructor that initialises the enumerator with the current instance of the data collection:

public TestCollectionEnnumerator(
 TestCollection Instance)
{
m_Instance = Instance;
}

The other enumeration methods have to now use m_Instance instead of “this” to make sure they work with the correct instance of the data:

void IEnumerator.Reset()
{
loc = -1;
}
object IEnumerator.Current
{
get
{
if (loc > -1)
return m_Instance.rnd.Next(500);
else
return -1;
}
}

bool IEnumerator.MoveNext()
{
loc++;
if (loc < m_Instance.Size)
return true;
else
return false;
}

 

We also need to change the data collection class so that its GetEnumerator actually creates a new instance of the enumerator:

class TestCollection:IEnumerable
{
private Random rnd;
private int Size;
IEnumerator IEnumerable.GetEnumerator()
{
return new
TestCollectionEnnumerator(this);
}

 

Notice how the use of “this” makes the final connection between the instance and any number of enumerators that are generated.

Now our example code is slightly more complicated but you can use it within nested for each loops and, as we shall see, nested LINQ queries.

 



Last Updated ( Thursday, 25 June 2020 )