Software Development

Simplify iterators by using the Yield statement

If you aren't using the Yield statement, learn how using the C# feature makes it easier to work with iterators.

The benefit of the Yield statement was not crystal clear to me when I first encountered it -- after all, it was introduced in C# 2.0. I recently revisited the topic with an iterator and instantly saw its value. Before I explain how to use the Yield statement, let's do a quick review of iterators.

Looking at iterators

MSDN defines an iterator as "a method, get accessor or operator that enables you to support foreach iteration in a class or struct without having to implement the entire IEnumerable interface." Instead of implementing the IEnumerable interface, an iterator is developed, which simply traverses the data structures in the class. The C# compiler automatically generates the Current, MoveNext, and Dispose methods when it detects the iterator. (These methods are part of the IEnumerable or IEnumerable<T> interface.) The following code snippet shows an iterator in use.

IList<string> SearchIt(IEnumerable<string> pList, string pSearchFor) {

var temp = new List<string>();

foreach (var cur in pList) {

if (cur.StartsWith(pSearchFor))

temp.Add(cur);

}

return temp;

}

The following code shows it in action.

static void Main(string[] args) {

IEnumerable<string> test = new string[]{"Tony", "John", "Timothy", "Anthony", "Julius"};

System.Collections.Generic.IList<string> t = SearchIt(test, "T");

foreach (string j in t) {

Console.WriteLine(j);

} }
It passes in an IEnumerable object and searches for values in it using the specified passed value in the second parameter. A List object is returned thatcontains all of the matches. This demonstrates one of the drawbacks of iterators with the overhead of maintaining state of where you are in a collection. In this case, we loop through the values with a foreach block and compile the matched items. Yield eliminates this problem by making the compiler do the heavy lifting of maintaining the state of the list -- that is, where you are in the list.

Using Yield

A good way to see what Yield can do is through a simple example. The following code alters the previous example to use Yield as opposed to creating and populating a List object.

static IEnumerable<string> SearchIt(IEnumerable<string> pList, string pSearchFor)

{

foreach (var cur in pList) {

if (cur.StartsWith(pSearchFor)) yield return cur;

} }

The method uses Yield to add values to the returned string value, and it keeps track of the current location with the passed in list. The following code utilizes the method using Yield.

static void Main(string[] args) {

IEnumerable<string> test = new string[]{"Tony", "John", "Timothy", "Anthony", "Julius"};

IEnumerable<string> t = SearchIt(test, "T");

foreach (string j in t) {

Console.WriteLine(j);

} }

As you can see, Yield is used to return items from a loop within a method and retain the state of the method through multiple calls.

One key point in our example is the amount of code in the Yield example -- it is much less than the first example where a List object had to be created and populated. The code iterates through the list of string values and adds matching values to the returned IEnumerable string object. All of the code to add and create the list of matching string values is handled by C#.

Another variation of Yield (the Yield break statement) allows you to halt the processing of values. When the system encounters Yield break, it stops the processing of object values at the current location. The following code snippet inserts a Yield statement for the purposes of the example -- basically, one match is found and the code exits.

foreach (var cur in pList) {

if (cur.StartsWith(pSearchFor)) {

yield return cur;

} else {

yield break;

} }

Yield simplifies working with iterators, but it does add overhead with the work assigned to the system. Theoretically, Yield could introduce performance issues, but this can only be revealed by thoroughly testing an application. It is worth noting that Yield can only appear inside an iterator block (which might be used as a body of a method, an operator, or an accessor), but it may not appear in an anonymous method. When used with an expression, it cannot appear in a try or catch block.

There are plenty of other C# features awaiting your use, so dig into the available options to take full advantage of the .NET environment. What are your favorite features of C#/.NET? Let us know in the discussion.

About

Tony Patton has worn many hats over his 15+ years in the IT industry while witnessing many technologies come and go. He currently focuses on .NET and Web Development while trying to grasp the many facets of supporting such technologies in a productio...

5 comments
nate.irvin
nate.irvin

"yield" is a useful piece of syntactic sugar, but be careful if you have a solution with mixed frameworks - if you have .NET 2.0 and .NET 4.0 projects in the same solution, and one of these projects uses yield, I've had the whole solution fail to compile, with the least helpful errors message ever. If you're using a single framework though, got to town!

Mark Miller
Mark Miller

I'm not up on what version of .Net is more commonly in use now, so I don't mean this as a slam against you, but I remember reading about yield, and this iterator technique you're talking about, back when .Net 2.0 was released. In .Net 3 or 4 I believe you can accomplish what you're talking about using the List container: List<string> list = new List<string> {"Tony", "John", "Timothy", "Anthony", "Julius"}; List<string> filteredList = list.FindAll(name => name.StartsWith("T"));

Mark Miller
Mark Miller

I think I got hung up on your example code, and missed your premise. I can see that for long lists what you're talking about would be valid. If you're iterating through the whole list, filtering, and carrying out an action, a simpler way of doing what you describe in the newer versions of .Net, using my code above, would be something like: list.ForEach(name => if (name.StartsWith("T")) {Console.WriteLine(name);}) though there would be no way to break out of the loop, unless you wanted to add an extension method to List that allowed you more control, which I guess would be just as much work as what you describe. So six of one, half dozen of the other.

Editor's Picks