A Beginner's Tutorial on Implementing IEnumerable Interface and Understanding yield Keyword
Introduction
In this article we will discuss about the
IEnumerable
interface. We will discuss how IEnumerable
interface facilitate the use of foreach
statement to iterate through a set of data. We will then look how to implement our own collections that implement IEnumerable
interface. The use of yield keyword and Enumerating generic collections will also be discussed in this article.Background
Whenever we work with collection of objects, we might find ourselves in need to iterate the collection. The best way to iterate through a collection is by implementing the Iterator pattern. (refer: Understanding and Implementing the Iterator Pattern in C# and C++[^]). C# provides a very clean construct of
foreach
statement to iterate in a read only manner over a collection.
C# also provides us the possibility of using the same
foreach
construct and all the enumeration techniques on our custom collection objects by implementing the IEnumerable interface. So let us see how we can implementIEnumerable
interface with our custom collection classes.Using the code
Enumerating the Collection classes
Before starting the discussion let us see how we can use the Built-in classes and iterate over them. Lets start by looking into the
ArrayList
class that implements IEnumerable
and see how we can have read only iteration over that using foreach
statement. // Let us first see how we can enumerate an object implementing IEnumerable
ArrayList list = new ArrayList();
list.Add("1");
list.Add(2);
list.Add("3");
list.Add('4');
foreach (object s in list)
{
Console.WriteLine(s);
}
Enumerating the Generic Collection classes
The
Arraylist
class is a generalized class that let us keep a collection. We can also have a generic class in which we can provide the type along with the data. Iterating over generic collection classes is also possible because they implement IEnumerable<T>
interface. Lets see how we can enumerate over a generic collection.// Let us first see how we can enumerate an object implementing IEnumerable<T>
List<string> listOfStrings = new List<string>();
listOfStrings.Add("one");
listOfStrings.Add("two");
listOfStrings.Add("three");
listOfStrings.Add("four");
foreach (string s in listOfStrings)
{
Console.WriteLine(s);
}
Now our objective is to have our own custom collection class and a generic collection class that should implement the
IEnumerable
and IEnumerable<T>
interface respectively to provide the possibility of enumerating over them.Understanding the yield keyword
Before jumping into the Implementation of these classes, we need to understand a very important keyword
yield
which actually facilitate the enumeration over collection. yield statement is used while returning a value from a function.
A normal method call like the one shown below will return only the first value no matter how many times it is called.
static int SimpleReturn()
{
return 1;
return 2;
return 3;
}
static void Main(string[] args)
{
// Lets see how simeple return works
Console.WriteLine(SimpleReturn());
Console.WriteLine(SimpleReturn());
Console.WriteLine(SimpleReturn());
Console.WriteLine(SimpleReturn());
}
The reason for this is that the normal return statement does not preserve the state of the function while returning. i.e. every call to this function is a new call and it will return the first value only.
Where as if I replace the return keyword by yield return then the function will become capable of saving its state while returning the value. i.e. when the function is called second time, it will continue the processing from where is has returned in the previous call.
static IEnumerable<int> YieldReturn()
{
yield return 1;
yield return 2;
yield return 3;
}
static void Main(string[] args)
{
// Lets see how yield return works
foreach (int i in YieldReturn())
{
Console.WriteLine(i);
}
}
When we run the above code it will return 1,2 and then 3. The only catch while using the
yield
return statement is that the function should return an IEnumerable
and should be called from an iteration block i.e. foreach
statement.Implementing IEnumerable in our custom Collection class
Now in our custom collection classes, if we define a function that will iterate over all the elements in the collection and return then using the
yield
keyword, we will be able to get hold of all the elements in the the collection.
So let us define our own
MyArrayList
class and implement IEnumerable
interface, which will force us to implement the GetEnumerator
function. This function will iterate over the collection and do a yield
return on all the elements.class MyArrayList : IEnumerable
{
object[] m_Items = null;
int freeIndex = 0;
public MyArrayList()
{
// For the sake of simplicity lets keep them as arrays
// ideally it should be link list
m_Items = new object[100];
}
public void Add(object item)
{
// Let us only worry about adding the item
m_Items[freeIndex] = item;
freeIndex++;
}
// IEnumerable Member
public IEnumerator GetEnumerator()
{
foreach (object o in m_Items)
{
// Lets check for end of list (its bad code since we used arrays)
if(o == null)
{
break;
}
// Return the current element and then on next function call
// resume from next element rather than starting all over again;
yield return o;
}
}
}
This class will now let us enumerate all the elements using a
foreach
stemement.static void Main(string[] args)
{
//Let us now go ahead and use our custom MyArrayList with IEnumerable implemented
MyArrayList myList = new MyArrayList();
myList.Add("1");
myList.Add(2);
myList.Add("3");
myList.Add('4');
foreach (object s in myList)
{
Console.WriteLine(s);
}
}
Note: This class is neither complete not a very good implementation. The only purpose of the sample implementation is to demonstrate the implementation of
IEnumerable
interface.Implementing IEnumerable<T> in our custom Generic Collection class
Let us now take this approach a little further and define a generic collection class capable of being enumerated. To do this we need to implement
IEnumerable<T>
interface. class MyList<T> : IEnumerable<T>
{
T[] m_Items = null;
int freeIndex = 0;
public MyList()
{
// For the sake of simplicity lets keep them as arrays
// ideally it should be link list
m_Items = new T[100];
}
public void Add(T item)
{
// Let us only worry about adding the item
m_Items[freeIndex] = item;
freeIndex++;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
foreach (T t in m_Items)
{
// Lets check for end of list (its bad code since we used arrays)
if (t == null) // this wont work is T is not a nullable type
{
break;
}
// Return the current element and then on next function call
// resume from next element rather than starting all over again;
yield return t;
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
// Lets call the generic version here
return this.GetEnumerator();
}
#endregion
}
This class will now let us enumrate all the elements using a
foreach
stemement.static void Main(string[] args)
{
// Let us first see how we can enumerate an custom MyList<t> class implementing IEnumerable<T>
MyList<string> myListOfStrings = new MyList<string>();
myListOfStrings.Add("one");
myListOfStrings.Add("two");
myListOfStrings.Add("three");
myListOfStrings.Add("four");
foreach (string s in myListOfStrings)
{
Console.WriteLine(s);
}
}
</t>
The following code example demonstrates the best practice for iterating a custom collection by implementing the IEnumerable and IEnumerator interfaces. In this example, members of these interfaces are not explicitly called, but they are implemented to support the use of foreach to iterate through the collection.
using System;
using System.Collections;
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
class App
{
static void Main()
{
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
}
/* This code produces output similar to the following:
*
* John Smith
* Jim Johnson
* Sue Rabon
*
*/
So now we have a collection class and a generic collectio class that implement
IEnumerable
and IEnumerable<T>
respectively. Althogh These class is neither complete not a very good implementation but they do serve the purpose of the article i.e. to demonstrate the implementation of IEnumerable
interface.Point of interest
What we have tried to do in this article is see how can we implement the
IEnumerable
and IEnumberable<T>
interface. We have looked into the significance of yield keyword. This stuff is known to most experienced C# programmers but the beginners might find it useful. I hope this has been informative.
No comments:
Post a Comment