Page 68 - MSDN Magazine, April 2017
P. 68
EssEntial .nEt MARK MICHAELIS Understanding C# foreach Internals and
Custom Iterators with yield
This month I’m going to explore the internals of a core construct of C# that we all program with frequently—the foreach statement. Given an understanding of the foreach internal behavior, you can then explore implementing the foreach collection interfaces using the yield statement, as I’ll explain.
Although the foreach statement is easy to code, I’m surprised at how few developers understand how it works internally. For exam ple, are you aware that foreach works differently for arrays than on IEnumberable<T> collections? How familiar are you with the relationship between IEnumerable<T> and IEnumerator<T>? And, if you do understand the enumerable interfaces, are you comfort able implementing them using yield?
What Makes a Class a Collection
By definition, a collection within the Microsoft .NET Framework is a class that, at a minimum, implements IEnumerable<T> (or the nongeneric type IEnumerable). This interface is critical because implementing the methods of IEnumerable<T> is the minimum needed to support iterating over a collection.
The foreach statement syntax is simple and avoids the complication of having to know how many elements there are.
The foreach statement syntax is simple and avoids the complica tion of having to know how many elements there are. The runtime doesn’t directly support the foreach statement, however. Instead, the C# compiler transforms the code as described in the next sections.
foreach with Arrays: The following demonstrates a simple foreach loop iterating over an array of integers and then printing out each integer to the console:
int[] array = new int[]{1, 2, 3, 4, 5, 6};
foreach (int item in array) {
Console.WriteLine(item); }
Figure 1 A Class Diagram of the IEnumerator<T> and IEnumerator Interfaces
From this code, the C# compiler creates a CIL equivalent of the for loop:
int[] tempArray;
int[] array = new int[]{1, 2, 3, 4, 5, 6};
tempArray = array;
for (int counter = 0; (counter < tempArray.Length); counter++) {
int item = tempArray[counter]; Console.WriteLine(item);
}In this example, note that foreach relies on the support for the Length property and the index operator ([]). With the Length prop erty, the C# compiler can use the for statement to iterate through each element in the array.
Figure 2 A Separate Enumerator Maintaining State During an Iteration
System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>();
int number; System.Collections.Generic.Stack<int>.Enumerator
enumerator; // ...
// If IEnumerable<T> is implemented explicitly, // then a cast is required.
// ((IEnumerable<int>)stack).GetEnumerator(); enumerator = stack.GetEnumerator();
while (enumerator.MoveNext()) {
number = enumerator.Current;
Console.WriteLine(number); }
Code download available at itl.tc/MSDN.2017.04.
54 msdn magazine