Introduction
IEnumerable
is an interface that has allowed for the creation of collections which can be iterated with a foreach
loop since .NET 1.0. Well, with the introduction of generics and the yield
keyword in .NET 2.0 things have gotten a whole lot simpler. This article will demonstrate how to use the generic IEnumerable<>
interface along with the new yield
keyword to create collections on the fly, without the need to implement all of the boiler plate code that was previously required.
Background
Before .NET 2.0, in order to create a collection that could be iterated using foreach
you had to implement a class that implemented IEnumerable
. This class would then have to implement IEnumerable.GetEnumerator()
, which would have to return an object that implements IEnumerator
. The developer would also have to create the class that is returned by IEnumerable.GetEnumerator()
. This class would have to implement three methods from the IEnumerator
interface. So a developer would have to create two custom classes and implement two interfaces and four methods in those two new classes.
As you can see, there is a fair amount of code that goes into creating a collection this way. This article is not going to cover the old way of doing things, as you can easily find a lot of resources on the web and MSDN that explain that process. Rather, this article will cover how to use IEnumerable<>
in conjunction with the new yield
keyword to create methods that return IEnumerable
collections in way less code than was previously possible.
The code
This code has a method called Helpers.GetIntCollectionFromString()
. It takes a string and returns an IEnumerable<int>
that can be iterated in a foreach
loop.
using System;
using System.Collections;
using System.Collections.Generic;
class Helpers
{
public static IEnumerable<int> GetIntCollectionFromString(
string stringToScan)
{
Console.WriteLine("Preparing to scan string");
string[] tokens = stringToScan.Split(' ');
int intToAdd;
foreach (string token in tokens)
{
if (int.TryParse(token, out intToAdd))
{
yield return intToAdd;
}
else
{
yield break;
}
}
}
}
class foo
{
static void Main()
{
string goodTestData = "1 2 3 4 5 6 7";
foreach (int listItem in Helpers.GetIntCollectionFromString(
goodTestData))
Console.WriteLine(listItem);
string badTestData = "1 2 3 afdf 4 5 6";
foreach (int listItem in Helpers.GetIntCollectionFromString(
badTestData))
Console.WriteLine(listItem);
Console.Read();
}
}
How it works
As you can see in the code below...
public static IEnumerable<int> GetIntCollectionFromString(
string stringToScan)
{
Console.WriteLine("Preparing to scan string");
string[] tokens = stringToScan.Split(' ');
int intToAdd;
foreach (string token in tokens)
{
if (int.TryParse(token, out intToAdd))
{
yield return intToAdd;
}
else
{
yield break;
}
}
}
...I have created a public static method that returns IEnumerable<int>
. This is the type that is needed to iterate a collection using foreach
loops. Then in Helpers.GetIntCollectionFromString()
each time I want to add an int
to the collection that will be returned, I use the new yield
keyword as show below:
yield return intToAdd;
When yield
is used in this way with return
, it acts differently from just using return
by itself. When return
is used by itself, the function returns and no more processing is performed. However, when it is used in conjunction with yield
, return
instead indicates to the runtime to add the value that would normally be returned to the collection that is to be returned, but to keep processing the function. So, in my code example, as long as there are items to be processed by...
foreach (string token in tokens)
...the function will not return. Instead, it will just keep telling the runtime via yield return
to add each item processed to the IEnumerable<int>
collection that will be returned when there is nothing left to process.
If you have been reviewing my code, by now you might be wondering what yield break
does. yield break
allows you to indicate that you are done processing and to return from the function. So, in my sample code, whenever a string that can't be converted to an int
is found, processing will stop and an IEnumerable<int>
collection will be returned with all items that were added via yield return
.
When you run my code you will get the output shown bellow:
Preparing to scan string
1
2
3
4
5
6
7
Preparing to scan string
1
2
3
Final Thoughts
I think this is a really handy new tool that allows for developers to quickly create functions that return custom collections without having to go through all the pain of implementing the non-generic
IEnumerable
and
IEnumerator
interfaces. I could easily imagine that you would find yourself using these types of collections a whole lot more often now that they have simplified their creation. Using
yeilds
it's also easier to create custom objects that can be iterated which will be the topic of my next article.
History
- 9 June, 2007 -- Added program output and a few gramar corrections
- 8 June, 2007 -- Original version posted