yield is a new contextual keyword introduced to C# 2.0, vital for lazy evaluation and the performance of queries in LINQ. Being a contextual keyword means that yield can be used as a variable name in C# without any problem. When put before return it becomes a keyword.
yield allows one enumerable class to be implemented in terms of another. This enables the delay of execution of queries until the latest possible moment, skipping the generation of intermediate results that would drastically reduce poerformance. The query operators in LINQ operate on sequence. The result of a query is often another sequence. Lazy evaluation means that until you iterate over the result of the query, the source of the query is not iterated.
To show you how yield works, let’s consider the same class I used in my last post, Winner.
public class Winner { string _name; string _country; int _year; public string Name { get { return _name; } set { _name = value; } } public string Country { get { return _country; } set { _country = value; } } public int Year { get { return _year; } set { _year = value; } } public Winner(string name, string country, int year) { _name = name; _country = country; _year = year; } }
and create a class WinnerDB, that contains a list of UEFA Champion League winners. This class implements IEnumerable and returns an enumerator, WinnerEnumerator, to be able to iterate over the winners.
public class WinnerEnumerator : IEnumerator { int pos = -1; private Winner[] _winners; public WinnerEnumerator(Winner[] winners) { _winners = winners; } public void Reset() { pos = -1; } public bool MoveNext() { pos++; return (pos < _winners.Length); } public object Current { get { try { return _winners[pos]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } } public class WinnersDB : IEnumerable { private Winner[] _winners; public WinnersDB() { _winners = new Winner[] { new Winner("Barcelona", "Spain", 2006), new Winner("Liverpool", "England", 2005), new Winner("FC Porto", "Portugal", 2004), new Winner("AC Milan", "Italy", 2003), new Winner("Real Madrid", "Spain", 2002), new Winner("Bayern Munchen", "Germany", 2001), new Winner("Real Madrid", "Spain", 2000), new Winner("Manchester Utd.", "England", 1999), new Winner("Real Madrid", "Spain", 1998), new Winner("Olimpique Marseille", "France", 1993), }; } public IEnumerator GetEnumerator() { return new WinnerEnumerator(_winners); } }
The usage of this class would look like this:
class Program { static void Main(string[] args) { WinnersDB db = new WinnersDB(); foreach (Winner w in db) { Console.WriteLine("{0}\t{1}, {2}", w.Year, w.Name, w.Country); } } }
and the output of the program
2006 Barcelona, Spain 2005 Liverpool, England 2004 FC Porto, Portugal 2003 AC Milan, Italy 2002 Real Madrid, Spain 2001 Bayern Munchen, Germany 2000 Real Madrid, Spain 1999 Manchester Utd., England 1998 Real Madrid, Spain 1993 Olimpique Marseille, France
So far so good. But with yield, you can let the compiler do all that stuff for you. When you use yield, the compiler generates an enumerator that keeps the current state of the iteration.
class Program { public static IEnumerable<Winner> WinnersDB() { Winner [] winners = new Winner[] { new Winner("Barcelona", "Spain", 2006), new Winner("Liverpool", "England", 2005), new Winner("FC Porto", "Portugal", 2004), new Winner("AC Milan", "Italy", 2003), new Winner("Real Madrid", "Spain", 2002), new Winner("Bayern Munchen", "Germany", 2001), new Winner("Real Madrid", "Spain", 2000), new Winner("Manchester Utd.", "England", 1999), new Winner("Real Madrid", "Spain", 1998), new Winner("Olimpique Marseille", "France", 1993), }; foreach (Winner w in winners) { yield return w; } } static void Main(string[] args) { foreach (Winner w in WinnersDB()) { Console.WriteLine("{0}\t{1}, {2}", w.Year, w.Name, w.Country); } } }
Running this code will produce the same output as the previous one, except that the implementation is much simpler. Perhaps the example is not the best, but should give you a hint of the use of the yield keyword. To see that the source is actually iterated only when the result is iterated, we can modify the WinnersDB method to print a message in the console:
foreach (Winner w in winners) { Console.WriteLine("yield: {0} {1}, {2}", w.Year, w.Name, w.Country); yield return w; }
In this case, the output looks like this:
yield: 2006 Barcelona, Spain 2006 Barcelona, Spain yield: 2005 Liverpool, England 2005 Liverpool, England yield: 2004 FC Porto, Portugal 2004 FC Porto, Portugal yield: 2003 AC Milan, Italy 2003 AC Milan, Italy yield: 2002 Real Madrid, Spain 2002 Real Madrid, Spain yield: 2001 Bayern Munchen, Germany 2001 Bayern Munchen, Germany yield: 2000 Real Madrid, Spain 2000 Real Madrid, Spain yield: 1999 Manchester Utd., England 1999 Manchester Utd., England yield: 1998 Real Madrid, Spain 1998 Real Madrid, Spain yield: 1993 Olimpique Marseille, France 1993 Olimpique Marseille, France
Actually, you do not need to build that array up front and the foreach in the sample, you can just yield return for each new Winner and be even more lazy.
e.g.
public static IEnumerable WinnerDB()
{
yield return new Winner(“Barcelona”, “Spain”, 2006);
yield return new Winner(“FC Porto”, “Portugal”, 2004);
…
}