Implement a method returning an IEnumerable (Iterators in C#)
Posted by: Damir Arh ,
on 11/10/2019,
in
Category C#
Abstract: Developers do not need to implement the IEnumerable and IEnumerator interfaces to return an IEnumerable. They can avoid writing all of that code by taking advantage of support for iterators in C#. This tutorial shows how.
Although the IEnumerable interface contains only a single method, that method returns an implementation of the IEnumerator interface. This means that both the IEnumerable and the IEnumerator interfaces will need to be developed at the same time. A majority of development effort will need to be put in the latter.
The class implementing the IEnumerator interface is often closely coupled to the matching class implementing the IEnumerable interface. Therefore, it’s a good practice to implement the IEnumerator interface as a private class nested inside the class implementing the IEnumerable interface. This prevents it from being instantiated on its own outside the parent class:
public class ArrayWrapper<T> : IEnumerable<T>
{
private readonly T[] array;
public ArrayWrapper(T[] array)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
this.array = array;
}
public IEnumerator<T> GetEnumerator() => new ArrayEnumerator(array);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private class ArrayEnumerator: IEnumerator<T>
{
private readonly T[] array;
public ArrayEnumerator(T[] array)
{
this.array = array;
}
// ...
}
}
The class in this piece of code we just saw will reimplement the IEnumerable interface over an array only to demonstrate how this could be done. There’s no practical need for doing that in production code because the array already implements the interface itself.
You will notice that I implemented both the generic and the non-generic version of the GetEnumerator method. This is required by the IEnumerable<T> interface so that the IEnumerable interface is also always implemented.
The same requirement is imposed by the IEnumerator<T> interface. To also implement the IEnumerator interface, both the generic and non-generic versions of the Current property must be implemented. Other members of both interfaces have matching signatures, so this is the only additional requirement.
The key part of a class implementing the IEnumerator interface is tracking the current position in the collection being iterated over:
private int index = -1;
public T Current => index >= 0 && index < array.Length ? array[index] : default(T);
object IEnumerator.Current => Current;
public bool MoveNext()
{
index++;
return index < array.Length;
}
In this implementation, the Current property returns the default value for the type when the enumerator is positioned before the first item or after the last item. This is not required by the interface specification as the value is not defined in such cases and the Current property should not even be accessed from consuming code.
There are two more methods that need to be implemented:
public void Reset()
{
index = -1;
}
public void Dispose()
{ }
The Reset method resets the position back to the beginning. However, it’s not required to be implemented and should not be relied upon in the consuming code. The method could simply throw a NotSupportedException when invoked.
The Dispose method is empty in my case because the class isn’t handling any unmanaged resources. Otherwise I would need to do all the cleanup inside it (e.g. close a connection to a database).
With all this code in place, a method returning an IEnumerable can now simply create a new instance of the ArrayWrapper<T> class:
public static IEnumerable<T> GetArrayWrapper<T>(T[] array) => new ArrayWrapper<T>(array);
Fortunately, you usually won’t need to implement the IEnumerable and IEnumerator interfaces to return an IEnumerable. You can avoid writing all of that code by taking advantage of support for iterators in C#. By using the yield return keywords, equivalent functionality can be achieved with much lesser code:
public static IEnumerable<T> GetArrayWrapperWithYield<T>(T[] array)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
for (var index = 0; index < array.Length; index++)
{
yield return array[index];
}
}
Apart from the null check, the method consists only of a simple loop, iterating over the items in the array and returning each one using the yield return statement. Every time this line is reached, the execution of the method will stop until the MoveNext method of the IEnumerator instance in use is called again. The compiler will automatically generate the code required to achieve such functionality. The generated code will actually be very similar to the hand coded implementation of the IEnumerable interface we just saw.
The yield return syntax is not only useful for iterating over collections stored in memory, it could just as well be used for retrieving data from external data sources involving I/O operations, e.g. files or databases.
The values returned could also be calculated on-the-fly as in the following example:
public static IEnumerable<long> GetFibonacciSequence()
{
yield return 1;
yield return 1;
long preprevious = 1;
long previous = 1;
while (true)
{
var current = preprevious + previous;
preprevious = previous;
previous = current;
yield return current;
}
}
The IEnumerable returned by this method will return the Fibonacci sequence of numbers, when iterated over (i.e. each number will be the sum of the previous two numbers). Since all the values are not stored in memory, but calculated as needed, the numbers could in theory be iterated over forever (if we ignore the problem of numeric overflow when the calculated value goes over the maximum value supported by the long data type).
While such an implementation might seem like a good idea, it can cause problems for consuming code which is not aware of the implementation details. When the IEnumerable returned by this method is used in a simple foreach loop, it will never exit. The same will happen if the ToArray or ToList LINQ method is called on it. It would be much safer if my GetFibonacciSequence method accepted a parameter with the stopping criteria for the loop, e.g. a maximum number of items or a maximum value returned.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Damir Arh has many years of experience with software development and maintenance; from complex enterprise software projects to modern consumer-oriented mobile applications. Although he has worked with a wide spectrum of different languages, his favorite language remains C#. In his drive towards better development processes, he is a proponent of Test-driven development, Continuous Integration, and Continuous Deployment. He shares his knowledge by speaking at local user groups and conferences, blogging, and writing articles. He is an awarded Microsoft MVP for .NET since 2012.