ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Implementing Custom Data Bindable Classes: IEnumerable

by James Still
06/30/2003

Most of us remain blissfully unaware of the implementation details behind the scenes when we use foreach to iterate over an array or some other collection. And as long as we use one of these out-of-the-box data structures, we don't have to worry about it. But many times, the standard collection classes fail to meet our specific needs. In the real world, our programs require custom collection classes that describe entities like automobiles or employees, so supporting enumeration for these classes becomes crucial. In this article, I'll explain how to build the necessary plumbing to support an enumerator for a custom collection class. I'll reuse the same Products class from the first article in this series, "Implementing Custom Data Bindable Classes: CollectionBase." In this article, however, instead of deriving from CollectionBase, we'll implement the IEnumerable interface directly.

IEnumerable

The IEnumerable interface defines a single GetEnumerator method. As you've probably guessed, the purpose of this method is to expose an enumerator that supports simple iteration over a collection of elements. The consumer of a class that implements IEnumerable needs to know very little about the implementation details or private data structures managing the collection of elements. The data structure could be a simple array of integers, a stack of strings, or other custom-built objects. All the consumer needs to know is the data type, so that it can use the foreach statement to iterate over each element in the collection:

foreach (int i in someIntArray) {
  // do something meaningful
}

We want that same sort of behavior in our Products class. That way, the client can enjoy the benefits of iterating with the simple foreach statement, without worrying about the implementation details behind the scenes. Three required class members support foreach: a Current property, and the Reset and MoveNext methods. These three members are defined by IEnumerator. The IEnumerable interface must return an instance of IEnumerator to the caller through its GetEnumerator method. But before we get to the actual interface implementation details, let's implement the bare bones Products class. Our Products class has an inline Product class that defines an element within the collection. To keep things simple, the Product has a single Name property of type string. Let's also use a private ArrayList within the Products class to maintain the collection:

public class Products : IEnumerable {

  private ArrayList productList = new ArrayList();

  public Products() {
    // default constructor
  }

  public Product this[int index] {
    get {
      return((Product)productList[index]);
    }
    set {
      productList[index] = value;
    }
  }

  public void Add(Product product) {
    productList.Add(product);
  }

  public int Count {
    get {
      return productList.Count;
    }
  }

  public class Product {

    private string productName;

    public Product(string Name) {
      productName = Name;
    }

    public string Name {
      get {
        return productName;
      }
      set {
        productName = value;
      }
    }
  }
}

As you can see, our Products class contains a single indexer, a Count property, and an Add method to enable the caller to add new elements to the productList ArrayList. The only thing missing is the IEnumerable.GetEnumerator method that is responsible for returning an IEnumerator to the caller. So we have to do two things: build another class that implements IEnumerator, and then implement GetEnumerator within Products to return an IEnumerator instance when the method is called.

Programming C#

Related Reading

Programming C#
By Jesse Liberty

IEnumerator

Let's start by coding the class that implements IEnumerator. As I mentioned earlier, IEnumerator has three members: a property called Current and two methods called MoveNext and Reset. These three, taken together, support the foreach statement. Let's call the new class that implements these interface members ProductEnumerator, and add it inline within the Products class:

public class ProductEnumerator : IEnumerator {

  private int idx = -1;
  private Products products;

  public ProductEnumerator(Products products) {
    this.products = products;
  }

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

  public object Current {
    get {
      if (idx > -1)
        return products[idx];
      else
        return -1;
    }
  }

  public bool MoveNext() {
    idx++;
    if (idx < products.Count)
      return true;
    else
      return false;
  }
}

This is pretty straightforward. We have an int idx that keeps track of the position in the productList ArrayList. Since the array is zero-based, Reset sets idx to -1 so that it is initialized to a position just before the first element in productList. A call to MoveNext first increments the index to the next element in the collection, and then returns true if it still points to a valid element, or false if it is out of bounds. Finally, a call to Current returns the Product object to which idx points. Now that we've implemented IEnumerator, we have something to return when the GetEnumerator method is called. So let's go ahead and add the GetEnumerator method to our Products class:

public IEnumerator GetEnumerator() {
  return new ProductEnumerator(this);
}

Client-Side

As you can see, the method expects the current Products class to be passed into ProductEnumerator's constructor, and then the enumerator is returned to the caller. Now we have a fully implemented custom collection class that correctly implements IEnumerable and returns an instance of an inline class that implements IEnumerator.

A consumer of the Products class (for instance, an ASP.NET web page) can build up a collection of Product objects and bind that collection to a list control in just two steps:

Products.Product p1 = new Products.Product("Ikura");
Products.Product p2 = new Products.Product("Chocolade");
Products.Product p3 = new Products.Product("Tofu");
Products.Product p4 = new Products.Product("Konbu");
Products products = new Products();
products.Add(p1);
products.Add(p2);
products.Add(p3);
products.Add(p4);

Repeater1.DataSource = products;
Repeater1.DataBind();

Or, if the caller needs to iterate over each Product object one at a time, it can do so in the usual way with the foreach syntax or by specifically iterating over the IEnumerator instance returned by a call to GetEnumerator:

IEnumerator productEnumerator = products.GetEnumerator();

while (productEnumerator.MoveNext()) {
  Products.Product p = (Products.Product)productEnumerator.Current;
  DropDownList1.Items.Add(p.Name);
}

Why would you ever want to iterate over an IEnumerator instance this way, when you can just use the foreach statement? That's a fair question. After all, the enumerator returned by a call to IEnumerator.GetEnumerator supports simple forward-only, read-only iteration. So retrieving and enumerating an IEnumerator amounts to the same thing as using the foreach statement. But what if you want to do something wild, like returning an enumerator that supports both forward and backward iteration over the elements in the collection? It might be useful if we exposed methods like MoveLast and MovePrevious to give the caller more options when consuming the data.

Extending IEnumerator

Your first instinct might be simply to add MovePrevious and MoveLast public methods to the existing ProductEnumerator class, but that won't exactly work. Remember that the call to GetEnumerator returns an instance of IEnumerator, so only the members defined by the interface are exposed to the client. Although the compiler won't complain, these additional methods will be unsupported. We need to find a way to return both the required IEnumerator instance and an enhanced IEnumerator instance that contains the new methods.

The answer lies with something called explicit interface implementation. This is a fancy phrase that just means you're fully qualifying the implementation of the interface method with the name of the interface itself. Thus, the method signature public object Current becomes object IEnumerator.Current when explicitly implemented. What's the difference? When a method or property is implemented explicitly, it is not available in a class instance, but only through its interface instance. You have to provide a second public member if you need to expose the implementation in a class instance. In effect, this means that you can implement against an interface and provide two different implementations of the same methods and properties. In this case, we want to use explicit interface implementations of GetEnumerator. The interface instance returns the required members of IEnumerator and the class instance returns these required members as well as the new MoveLast and MovePrevious methods. This might sound a little confusing at this point, but it will become clear in the code below. First we'll add our two new methods in the ProductEnumerator class:

public bool MovePrevious() {
  idx--;
  if (idx > -1)
    return true;
  else
    return false;
}

public void MoveLast() {
  idx = products.Count;
}

Next, we'll change our old class implementation of GetEnumerator to an explicit interface implementation, and then add a second GetEnumerator method that returns an instance of the ProductEnumerator class:

// explicit interface implementation
IEnumerator IEnumerable.GetEnumerator() {
  return (IEnumerator) new ProductEnumerator(this);
}

// class instance implementation

public ProductEnumerator GetEnumerator() {
  return new ProductEnumerator(this);
}

That's all there is to it. We're using the same method name in both cases, but the signatures reflect that one is explicitly implemented against the interface, while the other is implemented against the class deriving from the interface. Now the caller can retrieve the standard IEnumerator instance, just like before, or get a class instance of the ProductEnumerator with the two new exposed methods:

Products.ProductEnumerator extendedEnumerator = products.GetEnumerator();
extendedEnumerator.MoveLast();
while (extendedEnumerator.MovePrevious()) {
  Products.Product p = (Products.Product)extendedEnumerator.Current;
  DropDownList1.Items.Add(p.Name);
}

In this way, you can use explicit interface implementation to create type-safe members. Notice that in the code above, the call to Current is not type-safe. Instead, the client unboxes the object returned by Current and converts it to a Product object. If the object returned is not a Product object, then this causes an InvalidCastException to be thrown. To create a type-safe property, use explicit interface implementation to return the interface's type and then add another class implementation to return a specific type. The code sample below returns the Product.Name as a string type:

// explicit interface implementation
object IEnumerator.Current {
  get {
    if (idx > -1)
      return products[idx];
    else
  return -1;
  }
}

// class instance implementation
public string Current {
  get {
    return products[idx].Name;
  }
}

You just made the client code a whole lot simpler. Instead of casting the object to a Product in Current, you can simply access it directly:

Products.ProductEnumerator ExtendedEnumerator = products.GetEnumerator();
ExtendedEnumerator.MoveLast();
  while (ExtendedEnumerator.MovePrevious()) {
    DropDownList1.Items.Add(ExtendedEnumerator.Current);
}

Of course, this is just an example. In real-world applications, you don't have objects with single properties, so you'd have to handle this another way. But this does illustrate how to use explicit interface implementation to support two different implementations of a single member of that interface.

Conclusion

We've gone over the details of implementing the IEnumerable interface in a custom collection class. Because GetEnumerator returns an IEnumerator instance, we also had to derive a ProductEnumerator class from the IEnumerator interface. This meets the requirements for the foreach statement to support simple iteration over the elements in the collection. I also showed you how to use explicit interface implementation to extend the enumerator and expose two new MoveLast and MovePrevious methods. With these building blocks, you now have everything you need to implement IEnumerable in your own projects and to extend the required IEnumerator instance with new methods for your specific project requirements.

Related Links

James Still James Still is an experienced software developer in Portland, Oregon. He collaborated on "Visual C# .NET" (Wrox) and has immersed himself in .NET since the Beta 1 version was released.


Return to ONDotnet.com.

Copyright © 2009 O'Reilly Media, Inc.