How to: Access a Collection Class with foreach (C# Programming Guide)

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

Note

This example represents recommended practice only when you cannot use a generic collection class. Generics are supported in version 2.0 and later of the C# language and the .NET Framework. For an example of how to implement a type-safe generic collection class that supports IEnumerable<T>and thereby avoids the issues discussed later in this topic, see How to: Create an Iterator Block for a Generic List (C# Programming Guide).

In the following example, Tokens breaks the sentence "This is a sample sentence." into tokens by using ' ' and '-' as separators, and enumerates those tokens with the foreach statement:

Tokens f = new Tokens("This is a sample sentence.", new char[] {' ','-'});

foreach (string item in f)
{
    System.Console.WriteLine(item);
}

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

In C#, it is not absolutely 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 enabling you to define the return type of Current to be more specific than Object, which provides type-safety.

For example, starting with the sample code earlier in this topic, change the following lines:

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

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

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

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

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 sample sentence.", new char[] {' ','-'});

        foreach (string item in f)
        {
            System.Console.WriteLine(item);
        }
    }
}
/* Output:
    This
    is
    a
    sample
    sentence.  
*/

See Also

Concepts

C# Programming Guide

Reference

Arrays (C# Programming Guide)

Collection Classes (C# Programming Guide)

System.Collections.Generic

Other Resources

C# Reference