Export (0) Print
Expand All

Collection Classes Tutorial

Visual Studio .NET 2003

This tutorial shows how to implement a collection class that can be used with the foreach statement.

Sample Files

See Collection Classes Sample to download and build the sample files discussed in this tutorial.

Further Reading

Tutorial

The foreach statement is a convenient way to iterate over the elements of an array. It can also enumerate the elements of a collection, provided that the collection class has implemented the System.Collections.IEnumerator and System.Collections.IEnumerable interfaces.

Example 1

The following code sample illustrates how to write a collection class that can be used with foreach. The class is a string tokenizer, similar to the C run-time function strtok.

// tokens.cs
using System;
// The System.Collections namespace is made available:
using System.Collections;

// Declare the Tokens class:
public class Tokens : IEnumerable
{
   private string[] elements;

   Tokens(string source, char[] delimiters)
   {
      // Parse the string into tokens:
      elements = source.Split(delimiters);
   }

   // IEnumerable Interface Implementation:
   //   Declaration of the GetEnumerator() method 
   //   required by IEnumerable
   public IEnumerator GetEnumerator()
   {
      return new TokenEnumerator(this);
   }

   // Inner class implements IEnumerator interface:
   private class TokenEnumerator : IEnumerator
   {
      private int position = -1;
      private Tokens t;

      public TokenEnumerator(Tokens t)
      {
         this.t = t;
      }

      // Declare the MoveNext method required by IEnumerator:
      public bool MoveNext()
      {
         if (position < t.elements.Length - 1)
         {
            position++;
            return true;
         }
         else
         {
            return false;
         }
      }

      // Declare the Reset method required by IEnumerator:
      public void Reset()
      {
         position = -1;
      }

      // Declare the Current property required by IEnumerator:
      public object Current
      {
         get
         {
            return t.elements[position];
         }
      }
   }

   // Test Tokens, TokenEnumerator

   static void Main()
   {
      // Testing Tokens by breaking the string into tokens:
      Tokens f = new Tokens("This is a well-done program.", 
         new char[] {' ','-'});
      foreach (string item in f)
      {
         Console.WriteLine(item);
      }
   }
}

Output

This
is
a
well
done
program.

Code Discussion

In the preceding example, the following code is used to Tokens by breaking "This is a well-done program." into tokens (using ' ' and '-' as separators) and enumerating those tokens with the foreach statement:

Tokens f = new Tokens("This is a well-done program.", 
   new char[] {' ','-'});
foreach (string item in f)
{
   Console.WriteLine(item);
}

Notice that, internally, Tokens uses an array, which implements IEnumerator and IEnumerable itself. The code sample could have leveraged the array's enumeration methods as its own, but that would have defeated the purpose of this example.

In C#, it is not strictly necessary for a collection class to inherit from IEnumerable and IEnumerator in order to be compatible with foreach; as long as the class has the required GetEnumerator, MoveNext, Reset, and Current members, it will work with foreach. Omitting the interfaces has the advantage of allowing you to define the return type of Current to be more specific than object, thereby providing type-safety.

For example, starting with the sample code above, change the following lines:

public class Tokens  // no longer inherits from IEnumerable
public TokenEnumerator GetEnumerator()  // doesn't return an IEnumerator
public class TokenEnumerator  // no longer inherits from IEnumerator
public string Current  // type-safe: returns string, not object

Now, because Current returns a string, the compiler can detect when an incompatible type is used in a foreach statement:

foreach (int item in f)  // Error: cannot convert string to int

The disadvantage of omitting IEnumerable and IEnumerator is that the collection class is no longer interoperable with the foreach statements (or equivalents) of other common language runtime-compatible languages.

You can have the best of both worlds — type-safety within C# and interoperability with other common language runtime-compatible languages — by inheriting from IEnumerable and IEnumerator and using explicit interface implementation, as demonstrated in the following example.

Example 2

This sample is equivalent in function to Example 1, but it provides additional type-safety in C# while maintaining interoperability with other languages.

// tokens2.cs
using System;
using System.Collections;

public class Tokens: IEnumerable
{
   private string[] elements;

   Tokens(string source, char[] delimiters)
   {
      elements = source.Split(delimiters);
   }

   // IEnumerable Interface Implementation:

   public TokenEnumerator GetEnumerator() // non-IEnumerable version
   {
      return new TokenEnumerator(this);
   }

   IEnumerator IEnumerable.GetEnumerator() // IEnumerable version
   {
      return (IEnumerator) new TokenEnumerator(this);
   }

   // Inner class implements IEnumerator interface:

   public class TokenEnumerator: IEnumerator
   {
      private int position = -1;
      private Tokens t;

      public TokenEnumerator(Tokens t)
      {
         this.t = t;
      }

      public bool MoveNext()
      {
         if (position < t.elements.Length - 1)
         {
            position++;
            return true;
         }
         else
         {
            return false;
         }
      }

      public void Reset()
      {
         position = -1;
      }

      public string Current // non-IEnumerator version: type-safe
      {
         get
         {
            return t.elements[position];
         }
      }

      object IEnumerator.Current // IEnumerator version: returns object
      {
         get
         {
            return t.elements[position];
         }
      }
   }

   // Test Tokens, TokenEnumerator

   static void Main()
   {
      Tokens f = new Tokens("This is a well-done program.", 
         new char [] {' ','-'});
      foreach (string item in f) // try changing string to int
      {
         Console.WriteLine(item);
      }
   }
}

See Also

C# Tutorials

Show:
© 2014 Microsoft