Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# 2008 Step by Step.pdf
Скачиваний:
16
Добавлен:
25.03.2016
Размер:
13.96 Mб
Скачать

Chapter 19 Enumerating Collections

363

5.Add to the Main method the following statements shown in bold type that create and populate a binary tree of integers:

static void Main(string[] args)

{

Tree<int> tree1 = new Tree<int>(10); tree1.Insert(5);

tree1.Insert(11);

tree1.Insert(5); tree1.Insert(-12); tree1.Insert(15); tree1.Insert(0); tree1.Insert(14); tree1.Insert(-8); tree1.Insert(10);

}

6.Add a foreach statement, as follows in bold type, that enumerates the contents of the tree and displays the results:

static void Main(string[] args)

{

...

foreach (int item in tree1) Console.WriteLine(item);

}

7.Build the solution, correcting any errors if necessary.

8.On the Debug menu, click Start Without Debugging.

The program runs and displays the values in the following sequence:

–12, –8, 0, 5, 5, 10, 10, 11, 14, 15

9.Press Enter to return to Visual Studio 2008.

Implementing an Enumerator by Using an Iterator

As you can see, the process of making a collection enumerable can become complex and potentially error-prone. To make life easier, C# includes iterators that can automate much of this process.

An iterator is a block of code that yields an ordered sequence of values. Additionally, an iterator is not actually a member of an enumerable class. Rather, it specifies the sequence that an enumerator should use for returning its values. In other words, an iterator is just a de-

364

Part III Creating Components

 

scription of the enumeration sequence that the C# compiler can use for creating its own enu-

 

merator. This concept requires a little thought to understand it properly, so consider a basic

 

example before returning to binary trees and recursion.

A Simple Iterator

The following BasicCollection<T> class illustrates the principles of implementing an iterator. The class uses a List<T> object for holding data and provides the FillList method for populating this list. Notice also that the BasicCollection<T> class implements the IEnumerable<T> interface. The GetEnumerator method is implemented by using an iterator:

using System;

using System.Collections.Generic; using System.Collections;

class BasicCollection<T> : IEnumerable<T>

{

private List<T> data = new List<T>();

public void FillList(params T [] items)

{

foreach (var datum in items) data.Add(datum);

}

IEnumerator<T> IEnumerable<T>.GetEnumerator()

{

foreach (var datum in data) yield return datum;

}

IEnumerator IEnumerable.GetEnumerator()

{

// Not implemented in this example

}

}

The GetEnumerator method appears to be straightforward, but it bears closer examination. The first thing you should notice is that it doesn’t appear to return an IEnumerator<T>

type. Instead, it loops through the items in the data array, returning each item in turn. The key point is the use of the yield keyword. The yield keyword indicates the value that should be returned by each iteration. If it helps, you can think of the yield statement as calling a

temporary halt to the method, passing back a value to the caller. When the caller needs the next value, the GetEnumerator method continues at the point it left off, looping around and

then yielding the next value. Eventually, the data is exhausted, the loop finishes, and the GetEnumerator method terminates. At this point, the iteration is complete.

Chapter 19 Enumerating Collections

365

Remember that this is not a normal method in the usual sense. The code in the GetEnumerator method defines an iterator. The compiler uses this code to generate an implementation of the IEnumerator<T> class containing a Current and a MoveNext method. This implementation exactly matches the functionality specified by the GetEnumerator method.

You don’t actually get to see this generated code (unless you decompile the assembly containing the compiled code), but that is a small price to pay for the convenience and reduction in code that you need to write. You can invoke the enumerator generated by the iterator in the usual manner, as shown in this block of code:

BasicCollection<string> bc = new BasicCollection<string>(); bc.FillList(“Twas”, “brillig”, “and”, “the”, “slithy”, “toves”); foreach (string word in bc)

Console.WriteLine(word);

This code simply outputs the contents of the bc object in this order:

Twas, brillig, and, the, slithy, toves

If you want to provide alternative iteration mechanisms presenting the data in a different sequence, you can implement additional properties that implement the IEnumerable interface and that use an iterator for returning data. For example, the Reverse property of the BasicCollection<T> class, shown here, emits the data in the list in reverse order:

public IEnumerable<T> Reverse

{

get

{

for (int i = data.Count - 1; i >= 0; i--) yield return data[i];

}

}

You can invoke this property as follows:

BasicCollection<string> bc = new BasicCollection<string>(); bc.FillList(“Twas”, “brillig”, “and”, “the”, “slithy”, “toves”); foreach (string word in bc.Reverse)

Console.WriteLine(word);

This code outputs the contents of the bc object in reverse order:

toves, slithy, the, and, brillig, Twas

366 Part III Creating Components

Defining an Enumerator for the Tree<TItem> Class by Using an Iterator

In the next exercise, you will implement the enumerator for the Tree<TItem> class by using an

iterator. Unlike the preceding set of exercises, which required the data in the tree to be preprocessed into a queue by the MoveNext method, you can define an iterator that traverses the tree by using the more natural recursive mechanism, similar to the WalkTree method dis-

cussed in Chapter 18.

Add an enumerator to the Tree<TItem> class

1.Using Visual Studio 2008, open the BinaryTree solution located in the \Microsoft Press \Visual CSharp Step by Step\Chapter 19\IterarorBinaryTree folder in your Documents folder. This solution contains another copy of the BinaryTree project you created in Chapter 18.

2.Display the file Tree.cs in the Code and Text Editor window. Modify the definition of the Tree<TItem> class so that it implements the IEnumerable<TItem> interface, as shown in bold type here:

public class Tree<TItem> : IEnumerable<TItem> where TItem : IComparable<TItem>

{

...

}

3.Right-click the IEnumerable<TItem> interface in the class definition, point to Implement Interface, and then click Implement Interface Explicitly.

The IEnumerable<TItem>.GetEnumerator and IEnumerable.GetEnumerator methods are added to the class.

4.Locate the generic IEnumerable<TItem>.GetEnumerator method. Replace the contents of the GetEnumerator method as shown in bold type in the following code:

IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()

{

if (this.LeftTree != null)

{

foreach (TItem item in this.LeftTree)

{

yield return item;

}

}

yield return this.NodeData;

Chapter 19 Enumerating Collections

367

if (this.RightTree != null)

{

foreach (TItem item in this.RightTree)

{

yield return item;

}

}

}

It might not look like it at first glance, but this code follows the same recursive

algorithm that you used in Chapter 18 for printing the contents of a binary tree. If the LeftTree is not empty, the first foreach statement implicitly calls the GetEnumerator

method (which you are currently defining) over it. This process continues until a node is found that has no left subtree. At this point, the value in the NodeData property is

yielded, and the right subtree is examined in the same way. When the right subtree is exhausted, the process unwinds to the parent node, outputting the parent’s NodeData

property and examining the right subtree of the parent. This course of action continues until the entire tree has been enumerated and all the nodes have been output.

Test the new enumerator

1.In Solution Explorer, right-click the BinaryTree solution, point to Add, and then click Existing Project. In the Add Existing Project dialog box, move to the folder \Microsoft

Press\Visual CSharp Step By Step\Chapter 19\EnumeratorTest, select the EnumeratorTest project file, and then click Open.

This is the project that you created to test the enumerator you developed manually earlier in this chapter.

2.Right-click the EnumeratorTest project in Solution Explorer, and then click Set as Startup Project.

3.Expand the References node for the EnumeratorTest project in Solution Explorer. Right-click the BinaryTree assembly, and then click Remove.

This action removes the reference to the old BinaryTree assembly (from Chapter 18) from the project.

4.On the Project menu, click Add Reference. In the Add Reference dialog box, click the Projects tab. Select the BinaryTree project, and then click OK.

The new BinaryTree assembly appears in the list of references for the EnumeratorTest project in Solution Explorer.

Note These two steps ensure that the EnumeratorTest project references the version of the BinaryTree assembly that uses the iterator to create its enumerator rather than the earlier version.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]