Page 69 - MSDN Magazine, April 2017
P. 69

foreach with IEnumerable<T>: Although the preceding code works well on arrays where the length is fixed and the index oper­ ator is always supported, not all types of collections have a known number of elements. Furthermore, many of the collection classes, including Stack<T>, Queue<T> and Dictionary<TKey and TValue>, don’t support retrieving elements by index. Therefore, a more gen­ eral approach of iterating over collections of elements is needed. The iterator pattern provides this capability. Assuming you can determine the first, next, and last elements, knowing the count and supporting retrieval of elements by index is unnecessary.
The System.Collections.Generic.IEnumerator<T> and nonge­ neric System.Collections.IEnumerator interfaces are designed to enable the iterator pattern for iterating over collections of elements, rather than the length­index pattern shown previously. A class diagram of their relationships appears in Figure 1.
IEnumerator, which IEnumerator<T> derives from, includes three members. The first is bool MoveNext. Using this method, you can move from one element within the collection to the next, while at the same time detecting when you’ve enumerated through every item. The second member, a read­only property called Current, returns the element currently in process. Current is overloaded in IEnumerator<T>, providing a type­specific implementation of it. With these two members on the collection class, it’s possible to iterate over the collection simply using a while loop:
System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>();
int number; // ...
// This code is conceptual, not the actual code. while (stack.MoveNext())
{
number = stack.Current;
Console.WriteLine(number); }
In this code, the MoveNext method returns false when it moves past the end of the collection. This replaces the need to count elements while looping.
You can have multiple bookmarks, and moving any one of them enumerates over the collection independently of the others.
(The Reset method usually throws a NotImplementedException, so it should never be called. If you need to restart an enumeration, just create a fresh enumerator.)
The preceding example showed the gist of the C# compiler out­ put, but it doesn’t actually compile that way because it omits two important details concerning the implementation: interleaving and error handling.
State Is Shared: The problem with an implementation such as the one in the previous example is that if two such loops interleave msdnmagazine.com
Figure 3 Compiled Result of foreach on Collections
System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>();
System.Collections.Generic.Stack<int>.Enumerator enumerator;
IDisposable disposable;
enumerator = stack.GetEnumerator(); try
{
int number;
while (enumerator.MoveNext()) {
number = enumerator.Current;
Console.WriteLine(number); }
} finally {
// Explicit cast used for IEnumerator<T>. disposable = (IDisposable) enumerator; disposable.Dispose();
// IEnumerator will use the as operator unless IDisposable // support is known at compile time.
// disposable = (enumerator as IDisposable);
// if (disposable != null)
// {
// disposable.Dispose(); // }
}
each other—one foreach inside another, both using the same collection— the collection must maintain a state indicator of the current element so that when MoveNext is called, the next element can be determined. In such a case, one interleaving loop can affect the other. (The same is true of loops executed by multiple threads.)
To overcome this problem, the collection classes don’t support IEnumerator<T> and IEnumerator interfaces directly. Rather, there’s a second interface, called IEnumerable<T>, whose only method is GetEnumerator. The purpose of this method is to return an object that supports IEnumerator<T>. Instead of the collection class maintaining the state, a different class—usually a nested class so that it has access to the internals of the collection—will support the IEnumerator<T> interface and will keep the state of the iter­ ation loop. The enumerator is like a “cursor” or a “bookmark” in the sequence. You can have multiple bookmarks, and moving any one of them enumerates over the collection independently of the others. Using this pattern, the C# equivalent of a foreach loop will look like the code shown in Figure 2.
Cleaning up Following Iteration: Given that the classes that im­ plement the IEnumerator<T> interface maintain the state, sometimes
Figure 4 Error Handling and Resource Cleanup with using
System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>();
int number;
using( System.Collections.Generic.Stack<int>.Enumerator
enumerator = stack.GetEnumerator()) {
while (enumerator.MoveNext()) {
number = enumerator.Current;
Console.WriteLine(number); }
}
April 2017 55






















































   67   68   69   70   71