Chapter 7. Stepping through Collections

In This Chapter

  • Handling a directory as a collection of files and a file as a collection of bytes

  • Implementing a LinkedList collection

  • "Enumerating," or iterating, LinkedList

  • Implementing an indexer for easy access to collection objects

  • Easily looping through a collection with the C# iterator blocks

Chapter 6 in this minibook explores the collection classes provided by the .NET Framework class library for use with C# and other .NET languages. As you probably remember, collection classes are constructs in .NET that can be instantiated to hold groups of items. If you don't remember, you can read Chapter 6 for a reminder.

The first part of this chapter extends the notion of collections a bit. For instance, consider the following collections: a file as a collection of lines or records of data, and a directory as a collection of files. Thus this chapter builds on both the collection material in Chapter 6 of this minibook and the file material in Book III.

However, the focus in this chapter is on several ways to step through, or iterate, all sorts of collections, from file directories to arrays and lists of all sorts. You also see how to write your own collection class, or linked list.

Iterating through a Directory of Files

Reading and writing are the basic skills you need to get ahead in this world. That's what makes the FileRead and FileWrite programs in Book IV important. In some cases, however, you simply want to skim a directory of files, looking for something.

The following LoopThroughFiles program looks at all files in a given directory, reading each file and dumping out its contents in hexadecimal format to the console. (That may sound like a silly thing to do, but this program also demonstrates how to write out a file in a format other than just strings. I describe hexadecimal format in the following sidebar, "Getting hexed.")

Warning

If you run this program in a directory with lots of files, the hex dump can take a while. Also, long files take a while to loop through. Either pick a directory with few files or stop a lengthy program run by pressing Ctrl+C. This command interrupts a program running in any console window.

Note

// LoopThroughFiles -- Loop through all files contained in a directory;
//   this time perform a hex dump, though it could have been anything.
using System;
using System.IO;

namespace LoopThroughFiles
{
  public class Program
  {
    public static void Main(string[] args)
    {
      // If no directory name provided...
      string directoryName;
      if (args.Length == 0)
      {
        // ...get the name of the current directory...
        directoryName = Directory.GetCurrentDirectory();
      }
      else
      {
        // ...otherwise, assume that the first argument
        // is the name of the directory to use.
        directoryName = args[0];
      }
      Console.WriteLine(directoryName);

      // Get a list of all files in that directory.
      FileInfo[] files = GetFileList(directoryName);

      // Now iterate through the files in that list,
      // performing a hex dump of each file.
      foreach(FileInfo file in files)
      {
        // Write out the name of the file.
        Console.WriteLine("

hex dump of file {0}:", file.FullName);

        // Now "dump" the file to the console.
        DumpHex(file);

        // Wait before outputting next file.
        Console.WriteLine("
enter return to continue to next file");
        Console.ReadLine();
      }

      // That's it!
      Console.WriteLine("
o files left");

      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
// GetFileList -- Get a list of all files in a specified directory.
    public static FileInfo[] GetFileList(string directoryName)
    {
      // Start with an empty list.
      FileInfo[] files = new FileInfo[0];
      try
      {
        // Get directory information.
        DirectoryInfo di = new DirectoryInfo(directoryName);

        // That information object has a list of the contents.
        files = di.GetFiles();
      }
      catch(Exception e)
      {
        Console.WriteLine("Directory "{0}" invalid", directoryName);
        Console.WriteLine(e.Message);
      }
      return files;
    }

    // DumpHex -- Given a file, dump out the contents of the file to the console.
    public static void DumpHex(FileInfo file)
    {
      // Open the file.
      FileStream fs;
      BinaryReader reader;
      try
      {
        fs = file.OpenRead();
        // Wrap the file stream in a BinaryReader.
        reader = new BinaryReader(fs);
      }
      catch(Exception e)
      {
        Console.WriteLine("
can't read from "{0}"", file.FullName);
        Console.WriteLine(e.Message);
        return;
      }

      // Iterate through the contents of the file one line at a time.
      for(int line = 1; true; line++)
      {
        // Read another 10 bytes across (all that will fit on a single
        // line) -- return when no data remains.
        byte[] buffer = new byte[10];
        // Use the BinaryReader to read bytes.
        // Note: Using the bare FileStream would have been just as easy in this case.
        int numBytes = reader.Read(buffer, 0, buffer.Length);
        if (numBytes == 0)
        {
          return;
        }

        // Write out the data just read, in a single line preceded by line number.
        Console.Write("{0:D3} - ", line);
        DumpBuffer(buffer, numBytes);
// Stop every 20 lines so that the data doesn't scroll
        // off the top of the Console screen.
        if ((line % 20) == 0)
        {
          Console.WriteLine("Enter return to continue another 20 lines");
          Console.ReadLine();
        }
      }
    }

    // DumpBuffer -- Write a buffer of characters as a single line in hex format.
    public static void DumpBuffer(byte[] buffer, int numBytes)
    {
      for(int index = 0; index < numBytes; index++)
      {
        byte b = buffer[index];
        Console.Write("{0:X2}, ", b);
      }
      Console.WriteLine();
    }
  }
}

From the command line, the user specifies the directory to use as an argument to the program. The following command "hex-dumps" each file in the temp directory (including binary files as well as text files):

loopthroughfiles c:
andy	emp

If you don't enter a directory name, the program uses the current directory by default. (A hex dump displays the output as numbers in the hexadecimal — base 16 — system. See the nearby sidebar, "Getting hexed.")

Note

Both FileRead and FileWrite read the input filename from the console, whereas this program takes its input from the command line. I truly am not trying to confuse you — I'm trying to show different ways of approaching the same problem.

The first line in LoopThroughFiles looks for a program argument. If the argument list is empty (args.Length is zero), the program calls Directory.GetCurrentDirectory(). If you run inside Visual Studio rather than from the command line, that value defaults to the binDebug subdirectory of your LoopThroughFiles project directory.

Tip

The Directory class gives the user a set of methods for manipulating directories. The FileInfo class provides methods for moving, copying, and deleting files, among other tasks.

The program then creates a list of all files in the specified directory by calling the local GetFileList(). This method returns an array of FileInfo objects. Each FileInfo object contains information about a file — for example, the filename (with the full path to the file, FullName, or without the path, Name), the creation date, and the last modified date. Main() iterates through the list of files using your old friend, the foreach statement. It displays the name of each file and then passes off the file to the DumpHex() method for display to the console.

At the end of the loop, it pauses to allow the programmer a chance to gaze on the output from DumpHex().

The GetFileList() method begins by creating an empty FileInfo list. This list is the one it returns in the event of an error.

Note

Here's a neat trick to remember when coding any Get...List() method: If an error occurs, display an error message and return a zero-length list.

Warning

Be careful about returning a reference to an object. For instance, don't return a reference to one of the underlying queues wrapped up in the PriorityQueue class, described in Chapter 8 of this minibook — unless you want to invite folks to mess with those queues through the reference instead of through your class methods, that is. That's a sure ticket to a corrupt, unpredictable queue. But GetFileList() doesn't expose the innards of one of your classes here, so it's okay.

GetFileList() then creates a DirectoryInfo object. Just as its name implies, a DirectoryInfo object contains the same type of information about a directory that a FileInfo object does about a file: name, rank, and serial-number-type stuff. However, the DirectoryInfo object has access to one thing that a FileInfo doesn't: a list of the files in the directory, in the form of a FileInfo array.

As usual, GetFileList() wraps the directory- and file-related code in a big try block. The catch at the end traps any errors that are generated. Just to embarrass you further, the catch block flaunts the name of the directory (which probably doesn't exist, because you entered it incorrectly).

The DumpHex() method is a little tricky only because of the difficulties in formatting the output just right.

DumpHex() starts out by opening file. A FileInfo object contains information about the file — it doesn't open the file. DumpHex() gets the full name of the file, including the path, and then opens a FileStream in read-only mode using that name. The catch block throws an exception if FileStream can't read the file for some reason.

DumpHex() then reads through the file, 10 bytes at a time. It displays every 10 bytes in hexadecimal format as a single line. Every 20 lines, it pauses until the user presses Enter. I use the modulo operator, %, to accomplish that task.

Tip

Vertically, a console window has room for 25 lines by default. (The user can change the window's size, of course, allowing more or fewer lines.) That means you have to pause every 20 lines or so. Otherwise, the data just streams off the top of the screen before the user can read it.

The modulo operator (%) returns the remainder after division. Thus (line % 20) == 0 is true when line equals 20, 40, 60, 80 — you get the idea. This trick is valuable, useful in all sorts of looping situations where you want to perform an operation only so often.

DumpBuffer() writes out each member of a byte array using the X2 format control. Although X2 sounds like the name of a secret military experiment, it simply means "display a number as two hexadecimal digits."

The range of a byte is 0 to 255, or 0xFF — two hex digits per byte.

Here are the first 20 lines of the output.txt file (even its own mother wouldn't recognize this picture):

Hex dump of file C:C#ProgramsViholdtankTest2inoutput.txt:
001 - 53, 74, 72, 65, 61, 6D, 20, 28, 70, 72,
002 - 6F, 74, 65, 63, 74, 65, 64, 29, 0D, 0A,
003 - 20, 20, 46, 69, 6C, 65, 53, 74, 72, 65,
004 - 61, 6D, 28, 73, 74, 72, 69, 6E, 67, 2C,
005 - 20, 46, 69, 6C, 65, 4D, 6F, 64, 65, 2C,
006 - 20, 46, 69, 6C, 65, 41, 63, 63, 65, 73,
007 - 73, 29, 0D, 0A, 20, 20, 4D, 65, 6D, 6F,
008 - 72, 79, 53, 74, 72, 65, 61, 6D, 28, 29,
009 - 3B, 0D, 0A, 20, 20, 4E, 65, 74, 77, 6F,
010 - 72, 6B, 53, 74, 72, 65, 61, 6D, 0D, 0A,
011 - 20, 20, 42, 75, 66, 66, 65, 72, 53, 74,
012 - 72, 65, 61, 6D, 20, 2D, 20, 62, 75, 66,
013 - 66, 65, 72, 73, 20, 61, 6E, 20, 65, 78,
014 - 69, 73, 74, 69, 6E, 67, 20, 73, 74, 72,
015 - 65, 61, 6D, 20, 6F, 62, 6A, 65, 63, 74,
016 - 0D, 0A, 0D, 0A, 42, 69, 6E, 61, 72, 79,
017 - 52, 65, 61, 64, 65, 72, 20, 2D, 20, 72,
018 - 65, 61, 64, 20, 69, 6E, 20, 76, 61, 72,
019 - 69, 6F, 75, 73, 20, 74, 79, 70, 65, 73,
020 - 20, 28, 43, 68, 61, 72, 2C, 20, 49, 6E,
Enter return to continue another 20 lines

Note

You could reconstruct the file as a string from the hex display. The 0x61 value is the numeric equivalent of the character a. The letters of the alphabet are arranged in order, so 0x65 should be the character e; 0x20 is a space. The first line in this example (after the line number) is s) Nemo, where is a new line and is a carriage return. Intriguing, eh? That's about as far as I want to go. You can search Google or another search engine for ASCII table.

Those codes are also valid for the lower part of the much vaster Unicode character set, which C# uses by default. (You can look on a search engine on the Web for the term Unicode characters, and I explain the basics in the article "Converting Between Byte and Char Arrays" on the http://csharp102.info Web site.)

The following example shows what happens when the user specifies the invalid directory x:

Directory "x" invalid
Could not find a part of the path "C:C#ProgramsLoopThroughFilesinDebugx".

No files left
Press Enter to terminate...

Impressive, no?

Iterating foreach Collections: Iterators

In the rest of this chapter, you see three different approaches to the general problem of iterating a collection. In this section, I continue discussing the most traditional approach (at least for C# programmers), the iterator class, or enumerator, which implements the IEnumerator interface. As an example, I take you deeper into the iterator for the linked list, presented in the previous section.

Tip

The terms iterator and enumerator are synonymous. The term iterator is more common despite the name of the interface, but enumerator has been popular at Microsoft. Verb forms of these two nouns are also available: You iterate or enumerate through a container or collection. Note that the indexers and the new iterator blocks discussed later in this chapter are other approaches to the same problem.

Accessing a collection: The general problem

Different collection types may have different accessing schemes. Not all types of collections can be accessed efficiently with an index like an array's — the linked list, for example. Differences between collection types make it impossible to write a method such as the following without special provisions:

// Pass in any kind of collection:
void MyClearMethod(Collection aColl, int index)
{
  aColl[index] = 0; // Indexing doesn't work for all types of collections.
  // ...continues...
}

Each collection type can (and does) define its own access methods. For example, a linked list may offer a GetNext() method to fetch the next element in the chain of objects or a stack collection may offer a Push() and Pop() to add and remove objects.

A more general approach is to provide for each collection class a separate iterator class, which is wise in the ways of navigating that particular collection. Each collection X defines its own class IteratorX. Unlike X, IteratorX offers a common IEnumerator interface, the gold standard of iterating. This technique uses a second object, the iterator, as a kind of pointer, or cursor, into the collection.

The iterator (enumerator) approach offers these advantages:

  • Each collection class can define its own iteration class. Because the iteration class implements the standard IEnumerator interface, it's usually straightforward to code.

  • The application code doesn't need to know how the collection code works. As long as the programmer understands how to use the iterator, the iteration class can handle the details. That's good encapsulation.

  • The application code can create multiple independent iterator objects for the same collection. Because the iterator contains its own state information ("knows where it is," in the iteration), each iterator can navigate through the collection independently. You can have several iterations going at one time, each one at a different location in the collection.

To make the foreach loop possible, the IEnumerator interface must support all different types of collections, from arrays to linked lists. Consequently, its methods must be as general as possible. For example, you can't use the iterator to access locations within the collection class randomly because most collections don't provide random access. (You'd need to invent a different enumeration interface with that ability, but it wouldn't work with foreach.)

IEnumerator provides these three methods:

  • Reset(): Sets the enumerator to point to the beginning of the collection. Note: The generic version of IEnumerator, IEnumerator<T>, doesn't provide a Reset() method. With the generic LinkedList, just begin with a call to MoveNext().

  • MoveNext(): Moves the enumerator from the current object in the collection to the next one.

  • Current: A property, rather than a method, that retrieves the data object stored at the current position of the enumerator.

The following method demonstrates this principle. The programmer of the MyCollection class (not shown) creates a corresponding iterator class — say, IteratorMyCollection (using the IteratorX naming convention that I describe earlier in this chapter). The application programmer has stored numerous ContainedDataObjects in MyCollection. The following code segment uses the three standard IEnumerator methods to read these objects back out:

// The MyCollection class holds ContainedDataObject type objects as data.
void MyMethod(MyCollection myColl)
{
  // The programmer who created the MyCollection class also
  // creates an iterator class IteratorMyCollection;
  // the application program creates an iterator object
  // in order to navigate through the myColl object.
  IEnumerator iterator = new IteratorMyCollection(myColl);
  // Move the enumerator to the "next location" within the collection.
  while(iterator.MoveNext())
  {
    // Fetch a reference to the data object at the current location
    // in the collection.
    ContainedDataObject contained;  // Data
    contained = (ContainedDataObject)iterator.Current;
    // ...use the contained data object...
  }
}

The method MyMethod() accepts as its argument the collection of ContainedDataObjects. It begins by creating an iterator of class IteratorMyCollection. The method starts a loop by calling MoveNext(). On this first call, MoveNext() moves the iterator to the first element in the collection. On each subsequent call, MoveNext() moves the pointer "over one position." MoveNext() returns false when the collection is exhausted and the iterator cannot be moved any farther.

The Current property returns a reference to the data object at the current location of the iterator. The program converts the object returned into a ContainedDataObject before assigning it to contained. Calls to Current are invalid if the MoveNext() method didn't return true on the previous call or if MoveNext() hasn't yet been called.

Letting C# access data foreach container

The IEnumerator methods are standard enough that C# uses them automatically to implement the foreach statement.

The foreach statement can access any class that implements IEnumerable or IEnumerable<T>. I discuss foreach in terms of IEnumerable<T> in this section, as shown in this general method that is capable of processing any such class, from arrays to linked lists to stacks and queues:

void MyMethod(IEnumerable<T> containerOfThings)
{
  foreach(string s in containerOfThings)
  {
    Console.WriteLine("The next thing is {0}", s);
  }
}

A class implements IEnumerable<T> by defining the method GetEnumerator(), which returns an instance of IEnumerator<T>. Under the hood, foreach invokes the GetEnumerator() method to retrieve an iterator. It uses this iterator to make its way through the collection. Each element it retrieves has been cast appropriately before continuing into the block of code contained within the braces. Note that IEnumerable<T> and IEnumerator<T> are different, but related, interfaces. C# provides non-generic versions of both as well, but you should prefer the generic versions for their increased type safety.

IEnumerable<T> looks like this:

interface IEnumerable<T>
{
  IEnumerator<T> GetEnumerator();
}

while IEnumerator<T> looks like this:

interface IEnumerator<T>
{
  bool MoveNext();
  T Current { get; }
}

The nongeneric IEnumerator interface adds a Reset() method that moves the iterator back to the beginning of the collection, and its Current property returns type Object. Note that IEnumerator<T> inherits from IEnumerator — and recall that interface inheritance (covered in Book II, Chapter 8) is different from normal object inheritance.

C# arrays (embodied in the Array class they're based on) and all the .NET collection classes already implement both interfaces. So it's only when you're writing your own custom collection class that you need to take care of implementing these interfaces. For built-in collections, you can just use them. See the System.Collections.Generic namespace topic in Help.

Thus you can write the foreach loop this way:

foreach(int nValue in myCollection)
{
  // ...
}

Warning

I strongly advise you to use foreach to iterate collections rather than do it directly with IEnumerator or IEnumerator<T>. Chapter 8 of this minibook shows how easily you can go wrong with the raw iterator. The foreach loop is a helpful tool.

Accessing Collections the Array Way: Indexers

Accessing the elements of an array is simple: The command container[n] (read "container sub-n") accesses the nth element of the container array. The value in brackets is a subscript. If only indexing into other types of collections were so simple.

Stop the presses! C# enables you to write your own implementation of the index operation. You can provide an index feature for collections that wouldn't otherwise enjoy such a feature. In addition, you can index on subscript types other than the simple integers to which C# arrays are limited; for example, strings: for another example, try container["Joe"].

Indexer format

The indexer looks much like an ordinary get/set property, except for the appearance of the keyword this and the index operator [] instead of the property name, as shown in this bit of code:

class MyArray
{
  public string this[int index]   // Notice the "this" keyword.
  {
    get
    {
      return array[index];
    }
    set
    {
      array[index] = value;
    }
  }
}

Under the hood, the expression s = myArray[i]; invokes the get accessor method, passing it the value of i as the index. In addition, the expression myArray[i] = "some string"; invokes the set accessor method, passing it the same index i and "some string" as value.

An indexer program example

The index type isn't limited to int. You may choose to index a collection of houses by their owners' names, by house address, or by any number of other indices. In addition, the indexer property can be overloaded with multiple index types, so you can index on a variety of elements in the same collection.

The following Indexer program generates the virtual array class KeyedArray. This virtual array looks and acts like an array except that it uses a string value as the index:

Note

// Indexer -- This program demonstrates the use of the index operator
//    to provide access to an array using a string as an index.
//    This version is nongeneric, but see the IndexerGeneric example.
using System;

namespace Indexer
{
  public class KeyedArray
  {
    // The following string provides the "key" into the array --
    // the key is the string used to identify an element.
    private string[] _keys;

    // The object is the actual data associated with that key.
    private object[] _arrayElements;

    // KeyedArray -- Create a fixed-size KeyedArray.
    public KeyedArray(int size)
    {
      _keys = new string[size];
      _arrayElements = new object[size];
    }

    // Find -- Find the index of the element corresponding to the
    //    string targetKey (return a negative if it can't be found).
    private int Find(string targetKey)
    {
      for(int i = 0; i < _keys.Length; i++)
      {
        if (String.Compare(_keys[i], targetKey) == 0)
        {
          return i;
        }
      }
      return −1;
    }

    // FindEmpty -- Find room in the array for a new entry.
    private int FindEmpty()
    {
      for (int i = 0; i < _keys.Length; i++)
      {
        if (_keys[i] == null)
        {
          return i;
        }
      }
throw new Exception("Array is full");
    }

    // Look up contents by string key -- this is the indexer.
    public object this[string key]
    {
      set
      {
        // See if the string is already there.
        int index = Find(key);
        if (index < 0)
        {
          // It isn't -- find a new spot.
          index = FindEmpty();
          _keys[index] = key;
        }

        // Save the object in the corresponding spot.
        _arrayElements[index] = value;
      }

      get
      {
        int index = Find(key);
        if (index < 0)
        {
          return null;
        }
        return _arrayElements[index];
      }
    }
  }

  public class Program
  {
    public static void Main(string[] args)
    {
      // Create an array with enough room.
      KeyedArray ma = new KeyedArray(100);

      // Save the ages of the Simpson kids.
      ma["Bart"] = 8;
      ma["Lisa"] = 10;
      ma["Maggie"] = 2;

      // Look up the age of Lisa.
      Console.WriteLine("Let's find Lisa's age");
      int age = (int)ma["Lisa"];
      Console.WriteLine("Lisa is {0}", age);

      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
  }
}

The class KeyedArray holds two ordinary arrays. The _arrayElements array of objects contains the actual KeyedArray data. The strings that inhabit the _keys array act as identifiers for the object array. The ith element of _keys corresponds to the ith entry of _arrayElements. The application program can then index KeyedArray via string identifiers that have meaning to the application.

Tip

A noninteger index is referred to as a key. By the way, you can implement KeyedArray with an underlying List<T> instead of the fixed-size array. List<T> is indexable like an array because both implement the IList (or IList<T>) interface. This allows KeyedArray to be generic and to be much more flexible than using the inner array.

The set[string] indexer starts by checking to see whether the specified index already exists by calling the method Find(). If Find() returns an index, set[] stores the new data object into the corresponding index in _arrayElements. If Find() can't find the key, set[] calls FindEmpty() to return an empty slot in which to store the object provided.

The get[] side of the index follows similar logic. It first searches for the specified key using the Find() method. If Find() returns a nonnegative index, get[] returns the corresponding member of _arrayElements where the data is stored. If Find() returns −1, get[] returns null, indicating that it can't find the provided key anywhere in the list.

The Find() method loops through the members of _keys to look for the element with the same value as the string targetKey passed in. Find() returns the index of the found element (or −1 if none was found). FindEmpty() returns the index of the first element that has no key element.

Warning

Neither Find() nor FindEmpty() is written in an efficient manner. Any number of ways exist to make these methods faster, none of which has anything to do with indexers.

Note

Hey, wouldn't it be cool to provide an indexer for the LinkedList class? Sure, you can do that. But notice that even in KeyedArray, you must loop through the underlying _keys array to locate a specified key — which is why I provide Find() and FindEmpty(), which do just that. You would also have to implement an indexer for LinkedList by looping through the list, and the only way to do that is the same way you iterate it with LinkedListIterator — by following the forward links from node to node. An indexer would be convenient but wouldn't speed things up.

Note

Notice that you can't remove an element by providing a null key. As they used to say in college textbooks, "This problem is left as an exercise for the reader."

The Main() method demonstrates the Indexer class in a trivial way:

public class Program
{
  public static void Main(string[] args)
  {
    // Create an array with enough room.
    KeyedArray ma = new KeyedArray(100);

    // Save the ages of the Simpson kids.
    ma["Bart"] = 8;
    ma["Lisa"] = 10;
    ma["Maggie"] = 2;

    // Look up the age of Lisa.
    Console.WriteLine("Let's find Lisa's age");
    int age = (int)ma["Lisa"];
    Console.WriteLine("Lisa is {0}", age);

    // Wait for user to acknowledge the results.
    Console.WriteLine("Press Enter to terminate...");
    Console.Read();
  }
}

The program creates a KeyedArray object ma of length 100 (that is, with 100 free elements). It continues by storing the ages of the children in The Simpsons TV show, indexed by each child's name. Finally, the program retrieves Lisa's age using the expression ma["Lisa"] and displays the result. The expression ma["Lisa"] is read as "ma sub-Lisa."

Notice that the program has to cast the value returned from ma[] because KeyedArray is written to hold any type of object. The cast wouldn't be necessary if the indexer were written to handle only int values — or if the KeyedArray were generic. (For more information about generics, see Chapter 8 in this minibook.)

The output of the program is simple yet elegant:

Let's find Lisa's age
Lisa is 10
Press Enter to terminate...

Note

As an aside, the IList interface describes a class that provides an array-like integer indexer of the form object this[int]. C# also has an IList<T> interface, which you can use to replace object with your choice of type T. This would eliminate the need for a cast in the previous example.

Note

For a generic version of the Indexer program, see the IndexerGeneric example on this book's Web site.

Looping Around the Iterator Block

Here's a piece of the code from the Main() method to demonstrate the custom LinkedList (that chunk of code is in the LinkedListContainer program on this book's Web site):

public class Program
{
  public static void Main(string[] args)
  {
    // Create a container and add three elements to it.
    LinkedList llc = new LinkedList();
    LLNode first = llc.AddObject("This is first string");
    LLNode second = llc.AddObject("This is second string");
    LLNode third = llc.AddObject("This is last string");

    // Add one at the beginning and one in the middle.
    LLNode newfirst = llc.AddObject(null, "Insert before the first string");
    LLNode newmiddle = llc.AddObject(second, "Insert between the second and
       third strings");

    // You can manipulate the iterator "manually."
    Console.WriteLine("Iterate through the container manually:");
    LinkedListIterator lli = (LinkedListIterator)llc.GetEnumerator();
    lli.Reset();
    while(lli.MoveNext())
    {
      string s = (string)lli.Current;
      Console.WriteLine(s);
    }
    ...

This code gets a LinkedListIterator and uses its MoveNext() method and Current property to iterate a linked list. Just when you thought you had mastered iterating, it turns out that C# 2.0 has simplified this process so that

  • You don't have to call GetEnumerator() (and cast the results).

  • You don't have to call MoveNext().

  • You don't have to call Current and cast its return value.

  • You can simply use foreach to iterate the collection. (C# does the rest for you under the hood — it even writes the enumerator class.)

  • Well, to be fair, foreach works for the LinkedList class in this chapter, too. That comes from providing a GetEnumerator() method. But I still had to write the LinkedListIterator class ourselves. The new wrinkle is that you can skip that part in your roll-your-own collection classes, if you choose.

Rather than implement all those interface methods in collection classes you write, you can provide an iterator block — and you don't have to write your own iterator class to support the collection. Iterator blocks were introduced in C# 2.0, which shipped with Visual Studio 2005.

You can use iterator blocks for a host of other chores, too, as I show you in the next example.

The best approach to iteration now uses iterator blocks. When you write a collection class — and the need still exists for custom collection classes such as KeyedList and PriorityQueue — you implement an iterator block in its code rather than implement the IEnumerator interface. Then users of that class can simply iterate the collection with foreach. I walk you through it a piece at a time, to show you several variations on iterator blocks.

Every example in this section is part of the IteratorBlocks example on this book's Web site:

Note

// IteratorBlocks -- Demonstrates using the C# 2.0 iterator
//    block approach to writing collection iterators
using System;
namespace IteratorBlocks
{
  class IteratorBlocks
  {
    //Main -- Demonstrate five different applications of
    //   iterator blocks.
    static void Main(string[] args)
    {
      // Instantiate a MonthDays "collection" class.
      MonthDays md = new MonthDays();
      // Iterate it.
      Console.WriteLine("Stream of months:
");
      foreach (string month in md)
      {
        Console.WriteLine(month);
      }

      // Instantiate a StringChunks "collection" class.
      StringChunks sc = new StringChunks();
      // Iterate it: prints pieces of text.
      // This iteration puts each chunk on its own line.
      Console.WriteLine("
stream of string chunks:
");
      foreach (string chunk in sc)
      {
        Console.WriteLine(chunk);
      }
      // And this iteration puts it all on one line.
      Console.WriteLine("
stream of string chunks on one line:
");
      foreach (string chunk in sc)
      {
        Console.Write(chunk);
      }
      Console.WriteLine();

      // Instantiate a YieldBreakEx "collection" class.
      YieldBreakEx yb = new YieldBreakEx();
      // Iterate it, but stop after 13.
      Console.WriteLine("
stream of primes:
");
      foreach (int prime in yb)
      {
        Console.WriteLine(prime);
      }
// Instantiate an EvenNumbers "collection" class.
      EvenNumbers en = new EvenNumbers();
      // Iterate it: prints even numbers from 10 down to 4.
      Console.WriteLine("
stream of descending evens :
");
      foreach (int even in en.DescendingEvens(11, 3))
      {
        Console.WriteLine(even);
      }

      // Instantiate a PropertyIterator "collection" class.
      PropertyIterator prop = new PropertyIterator();
      // Iterate it: produces one double at a time.
      Console.WriteLine("
stream of double values:
");
      foreach (double db in prop.DoubleProp)
      {
        Console.WriteLine(db);
      }

      // Wait for the user to acknowledge.
      Console.WriteLine("Press enter to terminate...");
      Console.Read();

    }
  }

  // MonthDays -- Define an iterator that returns the months
  //   and their lengths in days -- sort of a "collection" class.
  class MonthDays
  {
    // Here's the "collection."
    string[] months =
            { "January 31", "February 28", "March 31",
              "April 30", "May 31", "June 30", "July 31",
              "August 31", "September 30", "October 31",
              "November 30", "December 31" };

    // GetEnumerator -- Here's the iterator. See how it's invoked
    //   in Main() with foreach.
    public System.Collections.IEnumerator GetEnumerator()
    {
      foreach (string month in months)
      {
        // Return one month per iteration.
        yield return month;
      }
    }
  }

  // StringChunks -- Define an iterator that returns chunks of text,
  //   one per iteration -- another oddball "collection" class.
  class StringChunks
  {
    // GetEnumerator -- This is an iterator; see how it's invoked
    //   (twice) in Main.
    public System.Collections.IEnumerator GetEnumerator()
    {
      // Return a different chunk of text on each iteration.
      yield return "Using iterator ";
      yield return "blocks ";
      yield return "isn't all ";
      yield return "that hard";
      yield return ".";
    }
  }
//YieldBreakEx -- Another example of the yield break keyword
  class YieldBreakEx
  {
    int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
    //GetEnumerator -- Returns a sequence of prime numbers
    //   Demonstrates yield return and yield break
    public System.Collections.IEnumerator GetEnumerator()
    {
      foreach (int prime in primes)
      {
        if (prime > 13) yield break;
        yield return prime;
      }
    }
  }

  //EvenNumbers -- Define a named iterator that returns even numbers
  //   from the "top" value you pass in DOWN to the "stop" value.
  //   Another oddball "collection" class
  class EvenNumbers
  {
    //DescendingEvens -- This is a "named iterator."
    //   Also demonstrates the yield break keyword.
    //   See how it's invoked in Main() with foreach.
    public System.Collections.IEnumerable DescendingEvens(int top,
                                                          int stop)
    {
      // Start top at nearest lower even number.
      if (top % 2 != 0) // If remainder after top / 2 isn't 0.
        top -= 1;
      // Iterate from top down to nearest even above stop.
      for (int i = top; i >= stop; i -= 2)
      {
        if (i < stop)
          yield break;
        // Return the next even number on each iteration.
        yield return i;
      }
    }
  }

  //PropertyIterator -- Demonstrate implementing a class
  //   property's get accessor as an iterator block.
  class PropertyIterator
  {
    double[] doubles = { 1.0, 2.0, 3.5, 4.67 };
    // DoubleProp -- A "get" property with an iterator block
    public System.Collections.IEnumerable DoubleProp
    {
      get
      {
        foreach (double db in doubles)
        {
          yield return db;
        }
      }
    }
  }
}

Note

For a more real-world illustration of iterator blocks, see the example PackageFactoryWithIterator, available with this chapter. The example extends the PriorityQueue example in Chapter 8 of this minibook.

Iterating days of the month: A first example

The following fragment from the IteratorBlocks example provides an iterator that steps through the months of the year:

//MonthDays -- Define an iterator that returns the months
//   and their lengths in days -- sort of a "collection" class.
class MonthDays
{
  // Here's the "collection."
  string[] months =
          { "January 31", "February 28", "March 31",
            "April 30", "May 31", "June 30", "July 31",
            "August 31", "September 30", "October 31",
            "November 30", "December 31" };

  //GetEnumerator -- Here's the iterator. See how it's invoked
  //   in Main() with foreach.
  public System.Collections.IEnumerator GetEnumerator()
  {
    foreach (string month in months)
    {
      // Return one month per iteration.
      yield return month;
    }
  }
}

Here's part of a Main() method that iterates this collection using a foreach loop:

// Instantiate a MonthDays "collection" class.
MonthDays md = new MonthDays();
// Iterate it.
foreach (string month in md)
{
  Console.WriteLine(month);
}

This extremely simple collection class is based on an array, as KeyedArray is. The class contains an array whose items are strings. When a client iterates this collection, the collection's iterator block delivers strings one by one. Each string contains the name of a month (in sequence), with the number of days in the month tacked on to the string. It isn't useful, but, boy, is it simple — and different!

The class defines its own iterator block, in this case as a method named GetEnumerator(), which returns an object of type System.Collections.IEnumerator. Now, it's true that you had to write such a method before, but you also had to write your own enumerator class to support your custom collection class. Here, you just write a fairly simple method to return an enumerator based on the new yield return keywords. C# does the rest for you: It creates the underlying enumerator class and takes care of calling MoveNext() to iterate the array. You get away with much less work and much simpler code.

Tip

Less code and less work fit my work ethic to a T.

Note

Your class containing the GetEnumerator() method no longer needs to implement the IEnumerator interface. In fact, you don't want it to.

In the following sections, I show you several varieties of iterator blocks:

  • Ordinary iterators

  • Named iterators

  • Class properties implemented as iterators

Note that class MonthDays' GetEnumerator() method contains a foreach loop to yield the strings in its inner array. Iterator blocks often use a loop of some kind to do this, as you can see in several later examples. In effect, you have in your own calling code an inner foreach loop serving up item after item that can be iterated in another foreach loop outside GetEnumerator().

What a collection is, really

Take a moment to compare the little collection in this example with an elaborate LinkedList collection. Whereas LinkedList has a complex structure of nodes connected by pointers, this little months collection is based on a simple array — with canned content, at that. I'm expanding the collection notion a bit, and I expand it even more before this chapter concludes.

(Your collection class may not contain canned content — most collections are designed to hold things you put into them via Add() methods and the like. The KeyedArray class in the earlier section "Accessing Collections the Array Way: Indexers," for example, uses the [] indexer to add items. Your collection could also provide an Add() method as well as add an iterator block so that it can work with foreach.)

The point of a collection, in the most general sense, is to store multiple objects and to allow you to iterate those objects, retrieving them one at a time sequentially — and sometimes randomly, or apparently randomly, as well, as in the Indexer example. (Of course, an array can do that, even without the extra apparatus of a class such as MonthDays, but iterators go well beyond the MonthDays example, as I'll show you.)

More generally, regardless of what an iterable collection does under the hood, it produces a "stream" of values, which you get at with foreach. (I cover file streams in Book III — I'm liberating the stream concept to make a point about iterators.)

To drive home the point, here's another simple collection class from IteratorBlocks, one that stretches the idea of a collection about as far as possible (you may think):

//StringChunks -- Define an iterator that returns chunks of text,
//   one per iteration -- another oddball "collection" class.
class StringChunks
{
  //GetEnumerator -- This is an iterator; see how it's invoked
  //   (twice) in Main.
  public System.Collections.IEnumerator GetEnumerator()
  {
    // Return a different chunk of text on each iteration.
    yield return "Using iterator ";
    yield return "blocks ";
    yield return "isn't all ";
    yield return "that hard";
    yield return ".";
  }
}

Oddly, the StringChunks collection stores nothing in the usual sense. It doesn't even contain an array. So where's the collection? It's in that sequence of yield return calls, which use a special syntax to return one item at a time until all have been returned. The collection "contains" five objects, each a simple string much like the ones stored in an array in the previous MonthDays example. And, from outside the class, in Main(), you can iterate those objects with a simple foreach loop because the yield return statements deliver one string at a time, in sequence. Here's part of a simple Main() method that iterates a StringChunks collection:

// Instantiate a StringChunks "collection" class.
StringChunks sc = new StringChunks();
// Iterate it: prints pieces of text.
foreach (string chunk in sc)
{
  Console.WriteLine(chunk);
}

Iterator syntax gives up so easily

As of C# 2.0, the language introduced two new bits of iterator syntax. The yield return statement resembles the old combination of MoveNext() and Current for retrieving the next item in a collection. The yield break statement resembles the C# break statement, which lets you break out of a loop or switch statement.

Yield return: Okay, I give up

The yield return syntax works this way:

  1. The first time it's called, it returns the first value in the collection.

  2. The next time it's called, it returns the second value.

  3. And so on. . . .

Using yield is much like calling an old-fashioned iterator's MoveNext() method explicitly, as in the LinkedList code. Each MoveNext() call produces a new item from the collection. But here you don't need to call MoveNext(). (You can bet, though, that it's being done for you somewhere behind that yield return syntax, and that's fine with us.)

You might wonder what I mean by "the next time it's called"? Here again, the foreach loop is used to iterate the StringChunks collection:

foreach (string chunk in sc)
{
  Console.WriteLine(chunk);
}

Each time the loop obtains a new chunk from the iterator (on each pass through the loop), the iterator stores the position it has reached in the collection (as all iterators do). On the next pass through the foreach loop, the iterator returns the next value in the collection, and so on.

Yield break: I want out of here!

I need to mention one bit of syntax related to yield. You can stop the progress of the iterator at some point by specifying the yield break statement in the iterator. Say a threshold is reached after testing a condition in the collection class's iterator block, and you want to stop the iteration at that point. Here's a brief example of an iterator block that uses yield break in just that way:

//YieldBreakEx -- Another example of the yield break keyword
class YieldBreakEx
{
  int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
  //GetEnumerator -- Returns a sequence of prime numbers
  //   Demonstrates yield return and yield break
  public System.Collections.IEnumerator GetEnumerator()
  {
    foreach (int prime in primes)
    {
      if (prime > 13) yield break;
      yield return prime;
    }
  }
}

In this case, the iterator block contains an if statement that checks each prime number as the iterator reaches it in the collection (using another foreach inside the iterator, by the way). If the prime number is greater than 13, the block invokes yield break to stop producing primes. Otherwise, it continues — with each yield return giving up another prime number until the collection is exhausted.

Tip

Besides using iterator blocks in formal collection classes, using them to implement enumerators, you could simply write any of the iterator blocks in this chapter as, say, static methods parallel to Main() in the Program class. In cases such as many of the examples in this chapter, the collection is inside the method. Such special-purpose collections can have many uses, and they're typically quick and easy to write.

Note

You can also write an extension method on a class (or another type) that behaves as an iterator block. That can be quite useful when you have a class that can be thought of in some sense as a collection. My favorite example comes from the Language Integrated Query (LINQ) realm in C# 3.0. Using a bit of C# reflection, you can get at the contents of a C# type, such as String, to enumerate its members. I give several examples of this concept in the MoreExtensionMethods example on this book's Web site. I cover extension methods in Book 2.

Iterator blocks of all shapes and sizes

In earlier examples in this chapter, iterator blocks have looked like this:

public System.Collections.IEnumerator GetEnumerator()
{
  yield return something;
}

But iterator blocks can also take a couple of other forms: as named iterators and as class properties.

An iterator named Fred

Rather than always write an iterator block presented as a method named GetEnumerator(), you can write a named iterator — a method that returns the System.Collections.IEnumerable interface instead of IEnumerator and that you don't have to name GetEnumerator() — you can name it something like MyMethod() instead.

For example, you can use this simple method to iterate the even numbers from a "top" value that you specify down to a "stop" value — yes, in descending order — iterators can do just about anything:

//EvenNumbers -- Define a named iterator that returns even numbers
//   from the "top" value you pass in DOWN to the "stop" value.
//   Another oddball "collection" class
class EvenNumbers
{
  //DescendingEvens -- This is a "named iterator."
  //   Also demonstrates the yield break keyword
  //   See how it's invoked in Main() with foreach.
  
public System.Collections.IEnumerable DescendingEvens(int top,
                                                      int stop)
  {
    // Start top at nearest lower even number.
    if (top % 2 != 0) // If remainder after top / 2 isn't 0.
      top -= 1;
    // Iterate from top down to nearest even above stop.
    for (int i = top; i >= stop; i -= 2)
    {
      if (i < stop)
        yield break;
      // Return the next even number on each iteration.
      yield return i;
    }
  }
}

The DescendingEvens() method takes two parameters (a handy addition), which set the upper limit of even numbers that you want to start from and the lower limit where you want to stop. The first even number that's generated will equal the top parameter or, if top is odd, the nearest even number below it. The last even number generated will equal the value of the stop parameter (or if stop is odd, the nearest even number above it). The method doesn't return an int itself, however; it returns the IEnumerable interface. But it still contains a yield return statement to return one even number and then waits until the next time it's invoked from a foreach loop. That's where the int is yielded up.

Note

This example shows another collection with no underlying collection — such as StringChunks, mentioned earlier in this chapter. Note that this one is computed — the method "yield returns" a computed value rather than a stored or hard-coded value. That's another way to implement a collectionless collection. (You can also retrieve items from a data source or Web service.) And, finally, the example shows that you can iterate a collection pretty much any way you like: down instead of up or by steps of two instead of one, for example.

Tip

An iterator needn't be finite, either. Consider the following iterator, which delivers a new number as long as you care to request them:

public System.Collections.IEnumerable PositiveIntegers()
{
  for (int i = 0; ; i++)
  {
    yield return i;
  }
}

Warning

This example is, in effect, an infinite loop. You might want to pass a value used to stop the iteration. Here's how you would call DescendingEvens() from a foreach loop in Main(). (Calling PositiveIntegers() in the preceding example would work similarly.) This example demonstrates what happens if you pass odd numbers as the limit values, too — another use of the % operator:

// Instantiate an EvenNumbers "collection" class.
EvenNumbers en = new EvenNumbers();
// Iterate it: prints even numbers from 10 down to 4.
Console.WriteLine("
stream of descending evens :
");
foreach (int even in en.DescendingEvens(11, 3))
{
  Console.WriteLine(even);
}

This call produces a list of even-numbered integers from 10 down through 4. Notice also how the foreach is specified. You have to instantiate an EvenNumbers object (the collection class). Then, in the foreach statement, you invoke the named iterator method through that object:

EvenNumbers en = new EvenNumbers();
foreach(int even in en.DescendingEvens(nTop, nStop)) ...

Tip

If DescendingEvens() were static, you wouldn't even need the class instance. You would call it through the class itself, as usual:

foreach(int even in EvenNumbers.DescendingEvens(nTop, nStop)) ...

It's a regular wetland out there!

If you can produce a "stream" of even numbers with a foreach statement, think of all the other useful things you may produce with special-purpose collections like these: streams of powers of two or of terms in a mathematical series such as prime numbers or squares — or even something exotic such as Fibonacci numbers. Or, how about a stream of random numbers (that's what the Random class already does) or of randomly generated objects?

Note

If you look at the PriorityQueue example in Chapter 8 of this minibook, you may want to check out the PackageFactoryWithIterator example — which appears only on this book's Web site. The example illustrates the use of an iterator block to generate a stream of randomly generated objects representing packages coming into a shipping company. It performs the same function as the PackageFactory class in the original PriorityQueue example, but with an iterator block.

Iterated property doesn't mean "a house that keeps getting sold"

You can also implement an iterator block as a property of a class — specifically in the get() accessor for the property. In this simple class with a DoubleProp property, the property's get() accessor acts as an iterator block to return a stream of double values:

//PropertyIterator -- Demonstrate implementing a class
//   property's get accessor as an iterator block.
class PropertyIterator
{
  double[] doubles = { 1.0, 2.0, 3.5, 4.67 };
  // DoubleProp -- A "get" property with an iterator block
  public System.Collections.IEnumerable DoubleProp
  {
    get
    {
      foreach (double db in doubles)
      {
        yield return db;
      }
    }
  }
}

You write the DoubleProp header in much the same way as you write the DescendingEvens() method's header in the named iterators example. The header returns an IEnumerable interface, but as a property it has no parentheses after the property name and it has a get() accessor — though no set(). The get() accessor is implemented as a foreach loop that iterates the collection and uses the standard yield return to yield up, in turn, each item in the collection of doubles.

Here's the way the property is accessed in Main():

// Instantiate a PropertyIterator "collection" class.
PropertyIterator prop = new PropertyIterator();
// Iterate it: produces one double at a time.
Console.WriteLine("
stream of double values:
");
foreach (double db in prop.DoubleProp)
{
  Console.WriteLine(db);
}

Tip

You can also have a generic iterator. Look up iterators, using in Help. The "Using Iterators" topic for C# includes an example of a named iterator that also happens to be generic.

Where you can put your iterator

Hmm, I have to be careful about my phrasing.

In the small special-purpose iterator classes in the IteratorBlocks example, earlier in this chapter, I put the collection itself inside the iterator class, as in MonthDays. In some cases, that's just right — for instance, when the collection is something like SentenceChunks, which returns canned bits of text, or something like DescendingEvens, in which the return is calculated on the spot. But suppose that you want to supply an iterator based on an iterator block for a real collection class, such as LinkedList.

That's what I did in the LinkedListWithIteratorBlock example on this book's Web site. That example rewrites the roll-your-own LinkedList with a GetEnumerator() method implemented as an iterator block. It completely replaces the old LinkedListIterator class. The following listing gives just the new version of GetEnumerator() (you can see the whole example on this book's Web site):

Note

// LinkedListWithIteratorBlock -- Implements iterator for the linked list as
//    an iterator block.
class LinkedList   // No longer need ": IEnumerator" here.
{
  ... rest of the class.
  ...
  // Here's the iterator, implemented as an iterator block.
  public IEnumerator GetEnumerator()
  {
    // Make sure the current node is legal.
    // If it's null, it hasn't yet been set to point into the list,
    // so point it at the head.
    if (currentNode == null)
    {
      currentNode = head;
    }
    // Here's the iteration for the enumerator that
    // GetEnumerator() returns.
    while (currentNode != null)
    {
      yield return currentNode.Object;
      currentNode = currentNode.forward;
    }
  }
}

I show the following basic form of an iterator block in several sections earlier in this chapter, including the section "Iterating days of the month: A first example":

public System.Collections.IEnumerator GetEnumerator() {}

This line looks exactly like the IEnumerator object returned by GetEnumerator() in the original LinkedList class. But implementing the GetEnumerator() method now works quite differently, as explained in this list:

Note

When you write an iterator block, C# creates the underlying LinkedListIterator class for you. You don't even write the class, and you never see its code. It's no longer part of the LinkedListWithIteratorBlock example.

  • In your GetEnumerator() method, you just use a loop to step through the linked list's nodes and yield return the data item stored at each node. You can see this code in the previous listing.

  • You no longer need to specify that your collection class implements IEnumerator, as in this class header:

    public class LinkedList      // No longer need ": IEnumerator" here

Well, of course, it isn't quite that simple

A few possible "gotchas" are inevitable here:

  • You have to make sure you start stepping at the beginning of the list.

  • To make this happen, the new LinkedList class adds a data member, currentNode, with which it tracks the iterator's progress through the list. (That member used to reside in the LinkedListIterator class.) The currentNode member is initially null, so the iterator needs to check for it. If so, it sets currentNode to point to the list's head node.

  • Unless head is null itself (the list is empty), currentNode is now non-null for the rest of the iteration. When the iterator finally reaches the end of the list, it indeed returns null, which signals the foreach loop to stop.

  • Each step through the list needs to do what MoveNext() used to do — advance to the next node; hence the while loop:

    // This does what MoveNext() did.
    while(currentNode != null)
    {
      // This does what Current did.
      yield return currentNode...;     // Portions omitted for a moment
      currentNode = currentNode.forward;
    }
  • Most iterator block implementations employ a loop to step through the collection — sometimes even an inner foreach loop. (But StringChunks shows that it isn't the only way.)

  • When you step through the list and start to yield return data, you have to dig inside the LLNode object to extract its stored data. A list node is just a storage bin for a string, an int, a Student, and so on. It requires that you yield return not currentNode, but, rather, this:

    yield return currentNode.Data;          // Now complete
    currentNode = currentNode.forward;
  • Under the hood, the original enumerator did that too. The Data property of LLNode returns the node's data as an object. I intentionally designed the original nongeneric linked list to be as general as possible — back in the bad old days before generics. Hence it stores objects.

Now the while loop, with its yield break statement, is doing the work that you used to have to do with more effort, and the resulting GetEnumerator() method works with foreach, as it did before, in Main().

If you think about it, this implementation simply moves the functionality of the old custom iterator class, LinkedListIterator, into the LinkedList class itself. It's all swept under the iterator block, you might say.

Under the hood, foreach makes the necessary cast for you (assuming that it's a legal cast). So if you stored strings in the list, your foreach loop looks like this:

foreach(string s in llc)  // foreach does the cast for you here.
{
  Console.WriteLine(s);
}

Note

For a generic version of this chapter's linked list, complete with an iterator block enumerator, see the GenericLinkedListContainer example on this book's Web site. (It isn't shown here.) That example demonstrates instantiating the generic LinkedList for string and then int. You'll get a kick out of stepping through the example in the debugger to see how foreach works. For comparison, see the new, built-in LinkedList<T> class in the System.Collections.Generic namespace.

Tip

Leave behind the whole nongeneric collection world — except for the good old array, which of course is still quite useful and still type-safe. Use generic collections. (Of course, I'm about to violate this tip in the next section.)

One more wrinkle

The original iterator implementation in LinkedList implemented its iterator as a separate iterator class designed as a companion to the LinkedList. That setup had one nice feature that's missing now from the iterator block versions I describe in the preceding section: You could easily create multiple instances of the iterator object and use each one for an independent iteration of the linked list. So iterator1 may be halfway through the list when iterator2 is just starting.

But the next example in this chapter remedies that problem (although for a simpler collection, not LinkedList). IteratorBlockIterator sticks with the separate companion iterator object, with intimate access to the collection's internal values. But that iterator object is itself implemented using an iterator block:

Note

// In file Program.cs:

// IteratorBlockIterator -- Implements a separate iterator object as a
//    companion to a collection class, a la LinkedList, but
//    implements the actual iterator with an iterator block
using System;
using System.Collections;
namespace IteratorBlockIterator
{
  class Program
  {
// Create a collection and use two iterator objects to iterate
    // it independently (each using an iterator block).
    static void Main(string[] args)
    {
      string[] strs = new string[] { "Joe", "Bob", "Tony", "Fred" };
      MyCollection mc = new MyCollection(strs);
      // Create the first iterator and start the iteration.
      MyCollectionIterator mci1 = mc.GetEnumerator();
      foreach (string s1 in mci1)  // Uses the first iterator object
      {
        // Do some useful work with each string.
        Console.WriteLine(s1);
        // Find Tony's boss.
        if (s1 == "Tony")
        {
          // In the middle of that iteration, start a new one, using
          // a second iterator, repeated for each outer loop pass.
          MyCollectionIterator mci2 = mc.GetEnumerator();
          foreach (string s2 in mci2)  // Uses the second iterator object
          {
            // Do some useful work with each string.
            if (s2 == "Bob")
            {
              Console.WriteLine("	{0} is {1}'s boss", s2, s1);
            }
          }
        }
      }
      // Wait for user to acknowledge the results.
      Console.WriteLine("Press Enter to terminate...");
      Console.Read();
    }
    // A simple collection of strings
    public class MyCollection
    {
      // Implement collection with an old-fashioned ArrayList.
      // Internal, so separate iterator object can access the strings.
      internal ArrayList _list = new ArrayList();
      public MyCollection(string[] strs)
      {
        foreach (string s in strs)
        {
          _list.Add(s);
        }
      }
      // GetEnumerator -- As in LinkedList, returns one of your
      //    iterator objects.
      public MyCollectionIterator GetEnumerator()
      {
        return new MyCollectionIterator(this);
      }
    }
    // MyCollectionIterator -- The iterator class for MyCollection
    //    (MyCollection is in a separate file.)
    public class MyCollectionIterator
    {
      // Store a reference to the collection.
      private MyCollection _mc;
      public MyCollectionIterator(MyCollection mc)
      {
        this._mc = mc;
      }
// GetEnumerator -- This is the iterator block, which carries
      //    out the actual iteration for the iterator object.
      public System.Collections.IEnumerator GetEnumerator()
      {
        // Iterate the associated collection's underlying list,
        // which is accessible because it's declared internal.
        foreach (string s in _mc._list)
        {
          yield return s;   // The iterator block's heart
        }
      }
    }
  }
}

The collection in IteratorBlockIterator isn't much to write home about: a simple class wrapped around a List of strings. Its GetEnumerator() method simply returns a new instantiation of the companion iterator class, as in LinkedList:

// GetEnumerator -- As in LinkedList, returns one of your
//    iterator objects.
public MyCollectionIterator GetEnumerator()
{
  return new MyCollectionIterator(this);
}

It's what's inside that iterator class that's interesting. It too contains a GetEnumerator() method. Implemented with an iterator block, this one does the iteration work. Here's that method:

// GetEnumerator -- This is the iterator block, which carries
//    out the actual iteration for the iterator object.
public System.Collections.IEnumerator GetEnumerator()
{
  // Iterate the associated collection's underlying list,
  // which is accessible because it's declared internal.
  foreach (string s in mc.list)
  {
    yield return s;   // The iterator block's heart
  }
}

This method has access to the companion collection's contained List<string>, so its yield return statement can return each string in turn.

But the payoff is in Main(), where two copies of the iterator object are created. The foreach loop for the second one is nested in the foreach loop for the first, so the output looks like this:

Joe
Bob
Tony
        Bob is Tony's boss
Fred

The indented line is produced by the nested iteration.

Here are those nested loops in Main() again:

MyCollectionIterator mci1 = mc.GetEnumerator();
foreach (string s1 in mci1)  // Uses the first iterator block
{
  // Do some useful work with each string.
  Console.WriteLine(s1);
  // Find Tony's boss.
  if(s1 == "Tony")
  {
    // In the middle of that iteration, start a new one, using
    // a second iterator; this is repeated for each outer loop pass.
    MyCollectionIterator mci2 = mc.GetEnumerator();
    foreach (string s2 in mci2)  // Uses the second iterator block
    {
      // Do some useful work with each string.
      if(s2 == "Bob")
      {
        Console.WriteLine("	{0} is {1}'s boss", s2, s1);
      }
    }
  }
}

The original iterator, with MoveNext() and Current, is still more flexible, but this example comes close — and is easier to do.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.240.249