Iterators, the magic of Whidbey C# compiler, let you write a collection type without even implementing IEnumerator. But, to understand how Iterators help us and how they work internally, we need to first look at how collections are implemented today. So, let's get started.
How collections work today
Collection, in its basic sense, is a container that holds objects of the same type. Developers work with collections in two different ways: One, they consume collections by iterating over them and working with the values they return and two, they implement collection types that can be iterated.
Typically, when one needs to implement a collection type, the type must implement a method by the name GetEnumerator that will return a reference to a type that implements the IEnumerator interface (which is defined in the System.Collections namespace). Here's an example of a type that implements a collection of integers.
Snapshot | |
Applies to | .Net developers |
Usp | Easier development methodology |
internal class MyIntCollection
{
public IEnumerator GetEnumerator()
{
return new MyIntEnumerator();
}
}
Above is the minimum code that you will need to write to start implementing a type that maintains a collection. However, the next major step, and the most important one, is where the developer needs to implement another type that implements the IEnumerator interface, and does the work of enumerating over the collection being maintained. In the above example, this type is MyIntEnumerator and is implemented as shown below.
internal class MyIntEnumerator: IEnumerator
{
// member working fields come here…
public MyIntEnumerator()
{
// perform some initialization, like setting the array size
}
public void Reset()
{
// reset the starting Index...
iCurIndex = -1;
bReachedEnd = false;
}
public bool MoveNext()
{
// move to the next value in collection
return true;
}
public object Current
{
// check for the valid current value and return it…
}
}
|
As you can see, this type:
l Does the hard work of maintaining the collection
l Needs to implement the IEnumerator interface so that the consumers can iterate over the collection data
l The developer of the type is responsible for coding all the implementation
The collection consume code is shown below.
MyIntCollection collInt = new MyIntCollection();
foreach(int iNum in collInt)
Console.WriteLine("{0} ", iNum);
Now, consider this: What if your class library has to implement multiple collections? The developers will need to do the plumbing work over and over again, in addition to implementing the IEnumerator repeatedly. Not only does this result in zero productivity gain, but also (almost) functional duplicity.
With Whidbey C#, all this comes to an end, with the compiler conjuring some more magic to ease the developers' lives. Let's see how.
Collections with Iterators
In Whidbey C#, a new keyword has been introduced that goes by the name yield. Developers of the Ruby programming language may be familiar with this. And once a developer uses yield in his code, he is freed from the implementation of IEnumerator! Yes, that's done by the compiler for you, during compile time.
Thus, Iterators provide a more simplified and clean way of working with collections and enumerators.
To understand the concept better, let's have a look at the Iterator version of the MyIntCollection class.
internal class MyIntCollection
{
private int iCount;
private int<> iData;
private int iCurIndex;
public MyIntCollection()
{
iCount = 10;
iData = new int
// fill up with some data
for(int i=0;i
iData = i + 1;
// set the starting index
iCurIndex = 0;
}
public IEnumerator GetEnumerator()
{
while(iCurIndex
yield iData
}
}
|
And that's all there is to it. No separate implementation of IEnumerator follows, unlike the previous example. Notice that although the GetEnumerator is supposed to return a type implementing IEnumerator, there's no such return statement within the method implementation that indicates so.
However, you do see the yield keyword in action, which yields the current value for the collection as per the enumeration cycle. But iData is an integer array, not IEnumerator implementation. So, isn't that a compiler error? No, it isn't. Let's see why.
Iterators–inside yield...
GetEnumerator does return an implementation of IEnumerator, even though the developer has not implemented it. When the compiler encounters the use of the yield keyword, it shall automatically produce code for such an implementation, behind the scenes. If you ILDASM (under Whidbey) the source code of the Iterators project that accompanies this article, you will see the compiler implemented version of the class that implements
IEnumerator.
As expected, though you used the yield keyword in the GetEnumerator method implementation, there's no reference to the same when you view the IL.
On the contrary, you will see the instantiation of the compiler-created class that implements
IEnumerator. This is expected since this is how enumerable types are implemented. We do the same work today manually. With Whidbey, this will be done by the compiler.
|
If you proceed to expand the node of the compiler-created IEnumerator implementation class, you will see the familiar implementations of Reset, MoveNext methods and the Current property.
All the work that the developer had to do regarding the implementation of looping through the values to be returned from the enumeration, maintaining counts, resetting the counters is now already done by the compiler. You simply use yield.
Finally
So, with this article we have concluded our coverage of the four core new enhancements in Whidbey: Generics, Partial Types, Anonymous Methods and Iterators. Keep the source code handy so that you can work with the Whidbey Beta when it comes out pretty soon.
Continuing our journey of exploring Whidbey, in our forthcoming issues we will talk about ADO.NET 2.0 and see how it has been enhanced to make the developer's more productive.
Kumar Gaurav Khanna
wintoolzone.com