Page 70 - MSDN Magazine, June 2017
P. 70

property returns a type corresponding to the return type of the iterator. As you saw in Figure 3, Pair<T> contains an iterator that returns a T type. The C# compiler examines the code contained within the iterator and creates the necessary code within the MoveNext method and the Current property to mimic its behavior. For the Pair<T> iterator, the C# compiler generates roughly equiv- alent code (see Figure 6).
Because the compiler takes the yield return statement and generates classes that correspond to what you probably would’ve written manually, iterators in C# exhibit the same performance characteristics as classes that implement the enumerator design pattern manually. Although there’s no performance improvement, the gains in programmer productivity are significant.
Creating Multiple Iterators in a Single Class
Previous iterator examples implemented IEnumerable<T>.Get- Enumerator, which is the method that foreach seeks implicitly. Sometimes you might want different iteration sequences, such as iterating in reverse, filtering the results or iterating over an object projectionotherthanthedefault.Youcandeclareadditionalitera- tors in the class by encapsulating them within properties or methods that return IEnumerable<T> or IEnumerable. If you want to iterate over the elements of Pair<T> in reverse, for example, you could provide a GetReverseEnumerator method, as shown in Figure 7.
Note that you return IEnumerable<T>, not IEnumerator<T>. This is different from IEnumerable<T>.GetEnumerator, which returns IEnumerator<T>. The code in Main demonstrates how to call GetReverseEnumerator using a foreach loop.
Yield Statement Requirements
You can use the yield return statement only in members that return an IEnumerator<T> or IEnumerable<T> type, or their non- generic equivalents. Members whose bodies include a yield return statement may not have a simple return. If the member uses the yield return statement, the C# compiler generates the necessary code to maintain the state of the iterator. In contrast, if the member uses the return statement instead of yield return, the programmer is responsible for maintaining his own state machine and returning
Figure 7 Using Yield Return in a Method That Returns IEnumerable<T>
an instance of one of the iterator interfaces. Further, just as all code paths in a method with a return type must contain a return statement accompanied by a value (assuming they don’t throw an exception), all code paths in an iterator must contain a yield return statement if they are to return any data.
If the member uses the yield­return­statement,­the­
C# compiler generates the necessary­code­to­maintain­the­
state­of­the­iterator.
The following additional restrictions on the yield statement result in compiler errors if they’re violated:
• The yield statement may appear only inside a method, a user-defined operator, or the get accessor of an indexer or property. The member must not take any ref or out parameter.
• The yield statement may not appear anywhere inside an anonymous method or lambda expression.
• The yield statement may not appear inside the catch and finally clauses of the try statement. Furthermore, a yield state- ment may appear in a try block only if there is no catch block.
Wrapping Up
Overwhelmingly, generics was the cool feature launched in C# 2.0, but it wasn’t the only collection-related feature introduced at the time. Another significant addition was the iterator. As I out- lined in this article, iterators involve a contextual keyword, yield, that C# uses to generate underlying CIL code that implements the iterator pattern used by the foreach loop. Furthermore, I detailed the yield syntax, explaining how it fulfills the GetEnumerator implementation of IEnumerable<T>, allows for breaking out of a loop with yield break and even supports a C# method that returns an IEnumeable<T>.
Much of this column derives from my “Essential C#” book (IntelliTect.com/EssentialCSharp), which I am currently in the midst of updating to “Essential C# 7.0.” For more information on this topic, check out Chapter 16. n
Mark Michaelis is founder of IntelliTect, where he serves as its chief tech- nical architect and trainer. For nearly two decades he has been a Microsoft MVP, and a Microsoft Regional Director since 2007. Michaelis serves on several Microsoft software design review teams, including C#, Microsoft Azure, SharePoint and Visual Studio ALM. He speaks at developer confer- ences and has written numerous books including his most recent, “Essential C# 6.0 (5th Edition)” (itl.tc/EssentialCSharp). Contact him on Facebook at facebook.com/Mark.Michaelis, on his blog at IntelliTect.com/Mark, on Twitter: @markmichaelis or via e-mail at mark@IntelliTect.com.
Thanks to the following IntelliTect technical experts for reviewing this article: Kevin Bost, Grant Erickson, Chris Finlayson, Phil Spokas and Michael Stokesbary
public struct Pair<T>: IEnumerable<T> {
...
public IEnumerable<T> GetReverseEnumerator() {
yield return Second;
yield return First; }
... }
public void Main() {
var game = new Pair<string>("Redskins", "Eagles"); foreach (string name in game.GetReverseEnumerator()) {
Console.WriteLine(name); }
}
66 msdn magazine
Essential .NET


































































   68   69   70   71   72