Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C#
Article

List processing using Yield and Delegates

Rate me:
Please Sign up or sign in to vote.
3.55/5 (6 votes)
4 May 20053 min read 50.1K   395   17   12
Instead of iterating through each element in a collection, we can write iterators to do specific tasks.

Introduction

This article will describe how we can use iterators to process IEnumerable lists. This method is not used very often in imperative programming languages like C#, Java so it is interesting to see how it works. Since this code uses yield (iterators) as well as anonymous delegates, it will only run with C# 2.0.

A simple yield method (iterator)

C#
class PositiveNumbers : System.Collections.IEnumerable
{
  public System.Collections.IEnumerator GetEnumerator()
  {
    int a = 1;
    while (true)
    {
      yield return a;
      a = a + 1;
    }
  }
}
...
foreach (int num in new PositiveNumbers())
{
  .. Do something with each positive number ..
}

Instead of writing a class implementing the three IEnumerator methods, we can now in C# 2.0 write a single method using the yield terminology. This method can be accessed from outside using the IEnumerator methods, MoveNext, Reset, Current. (Note: yield does not support Reset).

So how does it work? The answer is quite simple actually. The first time the MoveNext method is called, our method runs from the beginning. The variable a is set to 1, we enter the while loop and it returns a which equals 1. The next time MoveNext is called, we continue with the statement after the yield statement, a=a+1. This increases a to 2, we loop and again run the yield return statement. This time however a is increased to two. The following code demonstrates this more clearly:

C#
public System.Collections.IEnumerator GetEnumerator()
{
  yield return "First element";
  yield return "Second element";
  yield return "Third element";
  yield return "Last element";
}

This yield method (iterator) represents a list that always contains four string elements.

Some syntactic sugar

A problem with the above approach is that we need one class for each iterator. Fortunately C# 2.0 provides a solution for this. If the yield statement is put into a method which is declared to return an IEnumerable, the compiler will do everything for you:

C#
class Iterators
{
  public static System.Collections.IEnumerable PositiveNumbers()
  {
    int a = 1;
    while (true)
    {
      yield return a;
      a = a + 1;
    }
  }
}

Not only does this mean that we can put several iterators in the same class, but also access each iterator by their class and method name instead of using the new statement:

C#
foreach (int num in IteratorsPositiveNumbers()){}

Finally, since our iterator only returns integers, we can optimize it by using the generic version of IEnumerable. It is a simple change in the method header that could both increase the execution speed as well as reduce the amount of casts needed.

C#
public static System.Collections.Generic.IEnumerable<INT> PositiveNumbers()

Cutting the infinite list short

The PositiveNumbers enumerator above runs forever. If we only want some of the PositiveNumbers, what should we do? The answer is to create another iterator. This iterator will have three arguments:

  • The index of the first element we want.
  • The index of the last element we want.
  • An IEnumerable instance containing the source list.
C#
public static System.Collections.IEnumerable SubList(int start, 
                               int end, IEnumerable enumerable)
{
  int count = 0;
  foreach (object o in enumerable)
  {
    count++;
    if (count < start)
      continue;
    else if (count <= end)
      yield return o;
    else
      yield break;
  }
}
...
//This gives us a list containing number 5 through 15
IEnumerable numbers =
  Iterators.SubList(5, 15, Iterators.PositiveNumbers()))

Our iterator simply skips elements in the list we take in the argument until we reach the 5th element. We then yield return until we get to the 16th element. There we call yield break. Calling yield break is the same as saying that our collection doesn't contain any more elements and it terminates the method.

Mapping

The final iterator that we will study in this article is named from a function originally found in the LISP language. What Map will do is to take a method and a list. It will return a list where the method has been applied on each element in that list:

C#
 public delegate object MapFunction(object o);

  /// <summary>
  /// Runs the function on each element in the enumerable list
  /// </summary>
  public static IEnumerable Map(MapFunction mapFunction, 
                                 IEnumerable enumerable)
  {
    foreach (object o in enumerable)
    {
      yield return mapFunction(o);
    }
  }
...
foreach (int num in
        I.Map(
        /* Multiply each element by 2 */
        delegate(object o){  return ((int)o) * 2; },
        I.SubList(5, 15, I.PositiveNumbers())))
    {
      Console.Write("{0} ", num); //Print number 10 12 14 .. 28 30
    }

The code was pretty simple to write. We simply apply the MapFunction to each object and then yield return the result.

Some final thoughts

While most of what has been written in this article can be implemented in other ways, it is still interesting to see how it works when approaching it from the side of functional programming. It is especially interesting to notice how we can work on a list containing all positive numbers while only generating elements on demand, making it possible to use infinite lists.

Credits

Many thanks to Kris Vandermotten for pointing to me the fact that IEnumerable functions may contain yield. This, while forcing me to rewrite most of the article, increased its readability significantly and reduced the amount of code needed.

History

  • 2005-04-29: Posted the original article.
  • 2005-04-29: Reduced a few lines of code and fixed a minor error.
  • 2005-05-04: Big change due to new knowledge about yield and its syntactic sugar.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralDefinitely better after...yielding Pin
Mircea Puiu24-Jun-10 1:12
Mircea Puiu24-Jun-10 1:12 
GeneralA few comments... Pin
StealthyMark5-May-05 8:01
StealthyMark5-May-05 8:01 
GeneralRe: A few comments... Pin
Marcus Andrén5-May-05 12:25
Marcus Andrén5-May-05 12:25 
GeneralRe: A few comments... Pin
StealthyMark6-May-05 0:08
StealthyMark6-May-05 0:08 
GeneralRe: list index Pin
StealthyMark6-May-05 0:26
StealthyMark6-May-05 0:26 
GeneralRe: A few comments... Pin
Marcus Andrén5-May-05 14:31
Marcus Andrén5-May-05 14:31 
QuestionCan i do the Same on an Hashtable or a name value collection Pin
garapatiSridhar4-May-05 21:54
garapatiSridhar4-May-05 21:54 
AnswerRe: Can i do the Same on an Hashtable or a name value collection Pin
Marcus Andrén5-May-05 12:29
Marcus Andrén5-May-05 12:29 
GeneralSeveral iterators in a single class - the simple way Pin
Kris Vandermotten4-May-05 4:11
Kris Vandermotten4-May-05 4:11 
GeneralRe: Several iterators in a single class - the simple way Pin
Marcus Andrén4-May-05 5:50
Marcus Andrén4-May-05 5:50 
GeneralRe: Several iterators in a single class - the simple way Pin
Kris Vandermotten4-May-05 6:30
Kris Vandermotten4-May-05 6:30 
GeneralRe: Several iterators in a single class - the simple way Pin
Marcus Andrén4-May-05 8:27
Marcus Andrén4-May-05 8:27 
I have made some big changes to my article that I suggest your read before replying to this. I hope they should satisfy you mostly. Notice how I use a syntax very similar to yours, but using IEnumerable instead of IEnumerator.

This is not true, it's actually quite the opposite. In a way, an IEnumerator is the only thing you can do a foreach statement on. In fact, the only method in an IEnumerable is GetEnumerator, which returns the IEnumerator needed for the foreach statement.

Try compiling the code you wrote. It won't work. foreach specifically requires an IEnumerable object (or one implementing GetEnumerator). The following won't compile.

foreach (object o in new ArrayList().GetEnumerator()) { }


foreach won't work on an IEnumerator for good reason. An IEnumerator is not a collection, it is only meant to be a pointer into a single pass iteration over a collection. Trying to use it as a collection is plain ugly.

"Immutable" as in "unable to change"?

Immutable, as in that if I pass it as a parameter to a method, I can expect it to be the same after the method has been executed. The same as a when you pass a string variable as a method parameter you don't have to worry about the variable changing (unless you use ref).

In your example, it is absolutely not needed to traverse the list twice

First, a small nitpick. You can't use foreach on the IEnumerator, you have to do it manually instead. Smile | :) In this case it worked writing as you did. Still, the IEnumerator is changed during the method call which really isn't that pretty and you solve the problem by simply avoiding to call submethods (which you are forced to do since IEnumerators change when used so you can't be sure what the IEnumerator points at after the method has been executed.

Using IEnumerators instead of IEnumerable is the equivalent of using a char[] or List<char> instead of string. The char[] may be faster but it gets real ugly when you use it as a method parameter and it change.

It looks like you want to do LISP style programming in C#.

That is exactly why I wrote the article in the first place. Since yield is a functional concept introduced in c# 2.0, I wanted to see how it could be used in c# to do some functional programming. The article title should probably have been a clue that LISP inspired me. Big Grin | :-D The article you pointed to has made the concept even cleaner than I could ever have hoped for.

LISP is a great language, but in C# I usually use C# style, imperative, object-oriented programming.

It is, and I usually also do. I however still think that using IEnumerable is the object-oriented way. The IEnumerable.GetEnumerator() pattern exists for a reason, the IEnumerator isn't meant to represent a collection alone.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.