1. Trang chủ
  2. » Công Nghệ Thông Tin

Essential CSharp 3rd Edition_6 doc

98 260 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 98
Dung lượng 1,88 MB

Nội dung

ptg Iterators 639 CSharpPrimitiveTypes primitives = new CSharpPrimitiveTypes(); foreach (string primitive in primitives) { Console.WriteLine(primitive); } } } The results of Listing 16.13 appear in Output 16.5. The output from this listing is a listing of the C# primitive types. 1 Iterators and State When an iterator is first called in a foreach statement (such as foreach (string primitive in primitives) in Listing 16.13), its state is initialized within the enumerator. The iterator maintains its state as long as the foreach statement at the call site continues to execute. When you yield a value, process it, and resume the foreach statement at the call site, the iter- ator continues where it left off the previous time around the loop and OUTPUT 16.5: object byte uint ulong float char bool ushort decimal int sbyte short long void double string 1. In alpha versions of the C# 2.0 compiler, yield was a keyword rather than a contextual keyword. However, such a change could result in an incompatibility between C# 1.0 and C# 2.0. Instead, yield became a contextual keyword that must appear before return. As a result, no code-breaking change occurred because C# 1.0 did not allow any text (besides comments) prior to the return keyword. From the Library of Wow! eBook ptg Chapter 16: Building Custom Collections640 continues processing. When the foreach statement at the call site termi- nates, the iterator’s state is no longer saved. It is always safe to call the iter- ator again since the generated code never resets the state of the iterator but instead creates a new one when needed. Figure 16.8 shows a high-level sequence diagram of what takes place. Remember that the MoveNext() method appears on the IEnumerator<T> interface. Figure 16.8: Sequence Diagram with yield return Program primitives: CSharpPrimitiveTypes GetEnumerator() enumerator: Enumerator Instantiate yield return "object" yield return "byte" MoveNext() yield return "string" MoveNext() Console WriteLine() WriteLine() WriteLine() MoveNext() From the Library of Wow! eBook ptg Iterators 641 In Listing 16.13, the foreach statement at the call site initiates a call to GetEnumerator() on the CSharpPrimitiveTypes instance called primi- tives. Given the iterator instance (referenced by iterator), foreach begins each iteration with a call to MoveNext(). Within the iterator, you yield a value back to the foreach statement at the call site. After the yield return statement, the GetEnumerator() method seemingly pauses until the next MoveNext() request. Back at the call site, the foreach statement displays the yielded value on the screen. It then loops back around and calls MoveNext() on the iterator again. Notice that the second time, processing picks up at the second yield return statement. Once again, the foreach displays on the screen what CSharpPrimitiveTypes yielded and starts the loop again. This process continues until there are no more yield return statements within the iterator. At that point, the foreach loop at the call site terminates. More Iterator Examples Before you modify BinaryTree<T>, you must modify Pair<T> to support the IEnumerable<T> interface using an iterator. Listing 16.14 is an example that yields each element in Pair<T>. Listing 16.14: Using yield to Implement BinaryTree<T> public struct Pair<T>: IPair<T>, { public Pair(T first, T second) { _first = first; _second = second; } public T First { get{ return _first; } private set{ _first = value; } } private T _first; public T Second { get{ return _second; } private set{ _second = value; } } IEnumerable<T> From the Library of Wow! eBook ptg Chapter 16: Building Custom Collections642 private T _second; } In Listing 16.14, the iteration over the Pair<T> data type loops twice: first through yield return First, and then through yield return Second. Each time the yield return statement within GetEnumerator() is encoun- tered, the state is saved and execution appears to “jump” out of the GetEnumerator() method context and into the context of the call site. When the second iteration starts, GetEnumerator() begins to execute again with the yield return Second statement. System.Collections.Generic.IEnumerable<T> inherits from System. Collections.IEnumerable. Therefore, when implementing IEnumera- ble<T>, it is also necessary to implement IEnumerable. In Listing 16.14, you do so explicitly, and the implementation simply involves a call to IEnumera- ble<T>’s GetEnumerator() implementation. This call from IEnumerable. GetEnumerator() to IEnumerable<T>.GetEnumerator() will always work because of the type compatibility (via inheritance) between IEnumerable<T> and IEnumerable. Since the signatures for both GetEnumerator()s are identical (the return type does not distinguish a signature), one or both implementations must be explicit. Given the additional type safety offered by IEnumerable<T>’s version, you implement IEnumerable’s implementa- tion explicitly. Listing 16.15 uses the Pair<T>.GetEnumerator() method and displays "Inigo" and "Montoya" on two consecutive lines. #region IEnumerable<T> public IEnumerator<T> GetEnumerator() { yield return First; yield return Second; } #endregion IEnumerable<T> #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator { return GetEnumerator(); } #endregion From the Library of Wow! eBook ptg Iterators 643 Listing 16.15: Using Pair<T>.GetEnumerator() via foreach Pair<string> fullname = new Pair<string>("Inigo", "Montoya"); foreach (string name in fullname) { Console.WriteLine(name); } Notice that the call to GetEnumerator() is implicit within the foreach loop. Placing a yield return within a Loop It is not necessary to hardcode each yield return statement, as you did in both CSharpPrimitiveTypes and Pair<T>. Using the yield return state- ment, you can return values from inside a loop construct. Listing 16.16 uses a foreach loop. Each time the foreach within GetEnumerator() exe- cutes, it returns the next value. Listing 16.16: Placing yield return Statements within a Loop public class BinaryTree<T>: IEnumerable<T> { // #region IEnumerable<T> public IEnumerator<T> GetEnumerator() { // Return the item at this node. yield return Value; // Iterate through each of the elements in the pair. } #endregion IEnumerable<T> foreach (BinaryTree<T> tree in SubItems) { if (tree != null) { // Since each element in the pair is a tree, // traverse the tree and yield each // element. foreach (T item in tree) { yield return item; } } } From the Library of Wow! eBook ptg Chapter 16: Building Custom Collections644 #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } In Listing 16.16, the first iteration returns the root element within the binary tree. During the second iteration you traverse the pair of subele- ments. If the subelement pair contains a non-null value, then you traverse into that child node and yield its elements. Note that foreach(T item in tree) is a recursive call to a child node. As observed with CSharpPrimitiveTypes and Pair<T>, you can now iterate over BinaryTree<T> using a foreach loop. Listing 16.17 demon- strates this, and Output 16.6 shows the results. Listing 16.17: Using foreach with BinaryTree<string> // JFK jfkFamilyTree = new BinaryTree<string>( "John Fitzgerald Kennedy"); jfkFamilyTree.SubItems = new Pair<BinaryTree<string>>( new BinaryTree<string>("Joseph Patrick Kennedy"), new BinaryTree<string>("Rose Elizabeth Fitzgerald")); // Grandparents (Father's side) jfkFamilyTree.SubItems.First.SubItems = new Pair<BinaryTree<string>>( new BinaryTree<string>("Patrick Joseph Kennedy"), new BinaryTree<string>("Mary Augusta Hickey")); // Grandparents (Mother's side) jfkFamilyTree.SubItems.Second.SubItems = new Pair<BinaryTree<string>>( new BinaryTree<string>("John Francis Fitzgerald"), new BinaryTree<string>("Mary Josephine Hannon")); foreach (string name in jfkFamilyTree) { Console.WriteLine(name); } From the Library of Wow! eBook ptg Iterators 645 BEGINNER TOPIC struct versus class An interesting side effect of defining Pair<T> as a struct rather than a class is that SubItems.First and SubItems.Second cannot be assigned directly. The following will produce a compile error indicating that Sub- Items cannot be modified, “because it is not a variable”: jfkFamilyTree.SubItems.First = new BinaryTree<string>("Joseph Patrick Kennedy"); The issue is that SubItems is a property of type Pair<T>, a struct. There- fore, when the property returns the value, a copy of _SubItems is made, and assigning First on a copy that is promptly lost at the end of the state- ment would be misleading. Fortunately, the C# compiler prevents this. To overcome the issue, don’t assign it (see the approach in Listing 16.17), use class rather than struct for Pair<T>, don’t create a SubItems property and instead use a field, or provide properties in BinaryTree<T> that give direct access to _SubItems members. Canceling Further Iteration: yield break Sometimes you might want to cancel further iteration. You can do this by including an if statement so that no further statements within the code are executed. However, you can also jump back to the call site, causing MoveNext() to return false. Listing 16.18 shows an example of such a method. OUTPUT 16.6: John Fitzgerald Kennedy Joseph Patrick Kennedy Patrick Joseph Kennedy Mary Augusta Hickey Rose Elizabeth Fitzgerald John Francis Fitzgerald Mary Josephine Hannon From the Library of Wow! eBook ptg Chapter 16: Building Custom Collections646 Listing 16.18: Escaping Iteration via yield break public System.Collections.Generic.IEnumerable<T> GetNotNullEnumerator() { yield return Second; yield return First; } This method cancels the iteration if either of the elements in the Pair<T> class is null. A yield break statement is similar to placing a return statement at the top of a function when it is determined that there is no work to do. It is a way to exit from further iterations without surrounding all remaining code with an if block. As such, it allows multiple exits, and therefore, you should use it with caution because casual reading of the code may miss the early exit. ADVANCED TOPIC How Iterators Work When the C# compiler encounters an iterator, it expands the code into the appropriate CIL for the corresponding enumerator design pattern. In the generated code, the C# compiler first creates a nested private class to imple- ment the IEnumerator<T> interface, along with its Current property and a MoveNext() method. The Current property returns a type corresponding to the return type of the iterator. Listing 16.14 of 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 equivalent code (see Listing 16.19). Listing 16.19: C# Equivalent of Compiler-Generated C# Code for Iterators using System; using System.Collections.Generic; if((First == null) || (Second == null)) { yield break; } From the Library of Wow! eBook ptg Iterators 647 public class Pair<T> : IPair<T>, IEnumerable<T> { // // The iterator is expanded into the following // code by the compiler public virtual IEnumerator<T> GetEnumerator() { __ListEnumerator result = new __ListEnumerator(0); result._Pair = this; return result; } public virtual System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new GetEnumerator(); } private sealed class __ListEnumerator<T> : IEnumerator<T> { public __ListEnumerator(int itemCount) { _ItemCount = itemCount; } Pair<T> _Pair; T _Current; int _ItemCount; public object Current { get { return _Current; } } public bool MoveNext() { switch (_ItemCount) { case 0: _Current = _Pair.First; _ItemCount++; return true; case 1: _Current = _Pair.Second; _ItemCount++; From the Library of Wow! eBook ptg Chapter 16: Building Custom Collections648 return true; default: return false; } } } } Because the compiler takes the yield return statement and generates cla- sses that correspond to what you probably would have written manually, iterators in C# exhibit the same performance characteristics as classes that implement the enumerator design pattern manually. Although there is no performance improvement, the programmer productivity gained is significant. Creating Multiple Iterators in a Single Class Previous iterator examples implemented IEnumerable<T>.GetEnumera- tor(). This is the method that foreach seeks implicitly. Sometimes you might want different iteration sequences, such as iterating in reverse, fil- tering the results, or iterating over an object projection other than the default. You can declare additional iterators in the class by encapsulating them within properties or methods that return IEnumerable<T> or IEnu- merable. If you want to iterate over the elements of Pair<T> in reverse, for example, you provide a GetReverseEnumerator() method, as shown in Listing 16.20. Listing 16.20: Using yield return in a Method That Returns IEnumerable<T> public struct Pair<T>: IEnumerable<T> { { yield return Second; yield return First; } } public void Main() { Pair<string> game = new Pair<string>("Redskins", "Eagles"); public IEnumerable<T> GetReverseEnumerator() From the Library of Wow! eBook [...]... enumerate over all the types within an assembly, along with their members, and in the process create stubs for documentation of the assembly API You can then combine the metadata retrieved from reflection with the XML document created from XML comments (using the /doc switch) to create the API documentation Similarly, programmers use reflection metadata to generate code for persisting (serializing) business . with CSharpPrimitiveTypes and Pair<T>, you can now iterate over BinaryTree<T> using a foreach loop. Listing 16. 17 demon- strates this, and Output 16. 6 shows the results. Listing 16. 17:. ptg Iterators 63 9 CSharpPrimitiveTypes primitives = new CSharpPrimitiveTypes(); foreach (string primitive in primitives) { Console.WriteLine(primitive); } } } The results of Listing 16. 13 appear. Fitzgerald Mary Josephine Hannon From the Library of Wow! eBook ptg Chapter 16: Building Custom Collections6 46 Listing 16. 18: Escaping Iteration via yield break public System.Collections.Generic.IEnumerable<T>

Ngày đăng: 20/06/2014, 08:20

TỪ KHÓA LIÊN QUAN