Implementing Custom Data Bindable Classes: IEnumerable
by James Still06/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.
|
Related Reading Programming C# |
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);
}
Pages: 1, 2 |


