Thursday, November 15, 2007

Using the Yield Keyword

So, this has to be one of the more handy and obscure features of C# in .NET 2.0.

The yield keyword can only be used inside an iterator block, and what it allows you to do is easily populate a collection of items while iterating a collection of something else.  The example I put together below is pretty simple.  All it does is get a bunch of items from a database and then loads them into objects and uses the yield keyword to push them into the collection that is returned from the method.

Here's the method utilizing the yield keyword.  Notice there's no instantiation of the collection to put the objects in, the yield keyword tells the compiler to do it for you.

public static IEnumerable<MyObject> GetAllMyObjects()
{
    Database db = DatabaseFactory.CreateDatabase("default");
    using (IDataReader reader = db.ExecuteReader("GetAllMyObjects", new object[0]))
    {
        while (reader.Read())
        {
            yield return new MyObject(reader.GetInt32(0), reader.GetString(1));
        }
    }
}

And here's the calling method.

public static void Main(string[] args)
{
    foreach (MyObject myObject in GetAllMyObjects())
    {
        Console.WriteLine(myObject.Name);
    }
    Console.ReadLine();
}

Pretty cool stuff, cuts out a few lines of code.  Now, what the compiler does with this, is also equally interesting.  If you open the resulting binary up with reflector, you'll find that the compiler actually turns the "GetAllMyObjects" method into an IEnumerable<MyObject> class.  In this class, all the DB work is moved into the overriden MoveNext() method.  Here's what it looks like:

private bool MoveNext()
{
    try
    {
        switch (this.<>1__state)
        {
            case 0:
                this.<>1__state = -1;
                this.<db>5__1 = DatabaseFactory.CreateDatabase("default");
                this.<reader>5__2 = this.<db>5__1.ExecuteReader("GetAllMyObjects", new object[0]);
                this.<>1__state = 1;
                while (this.<reader>5__2.Read())
                {
                    this.<>2__current = new MyObject(this.<reader>5__2.GetInt32(0), this.<reader>5__2.GetString(1));
                    this.<>1__state = 2;
                    return true;
                Label_0091:
                    this.<>1__state = 1;
                }
                this.<>1__state = -1;
                if (this.<reader>5__2 != null)
                {
                    this.<reader>5__2.Dispose();
                }
                break;
 
            case 2:
                goto Label_0091;
        }
        return false;
    }
    fault
    {
        ((IDisposable) this).Dispose();
    }
}

Smart folks over there inside the C# team.

1 comments:

ameya said...

it seems like yield keyword is really helpful.
however can it be used with interface