Page 72 - MSDN Magazine, April 2017
P. 72
state can be quite complicated. To mitigate the challenges associated with implementing this pattern, C# 2.0 added the yield contextual keyword to make it easier for a class to dictate how the foreach loop iterates over its contents.
To correctly implement the iterator pattern, you need to maintain some internal state to keep track of where you are while enumerating the collection.
Defining an Iterator: Iterators are a means to implement meth ods of a class, and they’re syntactic shortcuts for the more complex enumerator pattern. When the C# compiler encounters an iterator, it expands its contents into CIL code that implements the enumerator pattern. As such, there are no runtime dependencies for implementing iterators. Because the C# compiler handles implementation through CIL code generation, there’s no real runtime performance benefit to using iterators. However, there is a substantial programmer pro ductivity gain in choosing iterators over manual implementation of the enumerator pattern. To understand this improvement, I’ll first consider how an iterator is defined in code.
Figure 7 A List of Some C# Keywords Output from the Code in Figure 6
Iterator Syntax: An iterator provides a shorthand implementa tion of iterator interfaces, the combination of the IEnumerable<T> and IEnumerator<T> interfaces. Figure 5 declares an iterator for the generic BinaryTree<T> type by creating a GetEnumerator method (albeit, with no implementation yet).
Yielding Values from an Iterator: The iterator interfaces are like functions, but instead of returning a single value, they yield a sequence of values, one at a time. In the case of BinaryTree<T>, the iterator yields a sequence of values of the type argument pro vided for T. If the nongeneric version of IEnumerator is used, the yielded values will instead be of type object.
To correctly implement the iterator pattern, you need to maintain some internal state to keep track of where you are while enumer ating the collection. In the BinaryTree<T> case, you track which elements within the tree have already been enumerated and which are still to come. Iterators are transformed by the compiler into a “state machine” that keeps track of the current position and knows how to “move itself ” to the next position.
The yield return statement yields a value each time an itera tor encounters it; control immediately returns to the caller that requested the item. When the caller requests the next item, the code begins to execute immediately following the previously executed yield return statement. In Figure 6, the C# builtin data type key words are returned sequentially.
The results of Figure 6 appear in Figure 7, which is a listing of the C# builtin types.
Clearly, more explanation is required but I’m out of space for this month so I’ll leave you in suspense for another column. Suffice it to say, with iterators you can magically create collections as properties, as shown in Figure 8—in this case, relying on C# 7.0 tuples just for the fun of it. For those of you wanting to look ahead, you can check out the source code or take a look at Chapter 16 of my “Essential C#” book.
Wrapping Up
In this column, I stepped back to functionality that’s been part of C# since version 1.0 and hasn’t changed much since the introduction of generics in C# 2.0. Despite the frequent use of this functionality, however, many don’t understand the details of what’s taking place internally. I then scratched the surface of the iterator pattern— leveraging the yield return construct—and provided an example.
Much of this column was pulled from my “Essential C#” book (IntelliTect.com/EssentialCSharp), which I’m currently in the midst of updating to “Essential C# 7.0.” For more information, check out Chapters 14 and 16. n
Mark Michaelis is founder of IntelliTect, where he serves as its chief technical architect and trainer. For nearly two decades he has been a Microsoft MVP, and a Microsoft Regional Director since 2007. Michaelis serves on several Micro- soft software design review teams, including C#, Microsoft Azure, SharePoint and Visual Studio ALM. He speaks at developer conferences 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.Mi- chaelis, 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
object byte uint ulong float char bool ushort decimal int sbyte short long void double string
Figure 8 Using yield return to Implement an IEnumerable<T> Property
IEnumerable<(string City, string Country)> CountryCapitals {
get {
yield return ("Abu Dhabi","United Arab Emirates"); yield return ("Abuja", "Nigeria");
yield return ("Accra", "Ghana");
yield return ("Adamstown", "Pitcairn");
yield return ("Addis Ababa", "Ethiopia"); yield return ("Algiers", "Algeria"); yield return ("Amman", "Jordan");
yield return ("Amsterdam", "Netherlands"); // ...
} }
58 msdn magazine
Essential .NET