It doesn't take long in one's c# education before one discovers the 'foreach' loop. What they may not know is that this is actually the Iterator pattern built into the language through the IEnumerator and IEnumerable interfaces.
In this article I'll go over the pattern and show you how to build an iterator from scratch. While you may never implement this exact pattern in any of your projects it's good to know how it's done and will hopefully help you understand how to extend the built-in iteration system within the c# language.
Ignoring our 'foreach' capabilities for the moment let's see what the problem is.
//Here's a simple class that can be kept in
//some sort of collection. We don't know what kind of
//collection but we'd like to go through each Item and
//print out the description.
class Item
{
string _description;
public string Description
{
get { return _description; }
}
public Item(string description)
{
_description = description;
}
}
class Program
{
static void Main(string[] args)
{
List<Item> itemList = new List<Item>();
int maxArraySize = 3;
Item[] itemArray = new Item[3];
//populate the collections.
for (int i = 0; i < maxArraySize; i++)
{
Item ithItem = new Item("Item" + i.ToString());
itemArray[i] = ithItem;
itemList.Add(ithItem);
}
//Iterate over the List...
for (int i = 0; i < itemList.Count; i++)
{
Console.WriteLine(itemList[i].Description);
}
//Iterate over the Array...
for (int = 0; i < itemArray.Length; i++)
{
Console.WriteLine(itemArray[i].Description);
}
}
}
Again, temporarily forgetting about the 'foreach' loop (we'll get to it), this is a mess with no obvious solution. For every type of collection we have we'd need to loop over it in a different way. The List has a Length property while arrays have a Count property and there's no telling how some other collection may be looped through.
Let's make a simple Store interface to help illustrate. Some stores may want use Arrays, and some may want to use Lists or any other type of collection at all, but they all need to be able to print out the description of their inventory. A Store can be thought of here as an Item collection.
interface IStore
{
void PrintInventory();
}
public class ListStore : IStore
{
List<Item> _inventory;
public ListStore()
{
_inventory.Add(new Item("Soap"));
_inventory.Add(new Item("Fan"));
_inventory.Add(new Item("Fish Tank"));
}
public void PrintInventory()
{
//Here's the problem...
for (int i = 0; i = _inventory.Count; i++)
{
Console.WriteLine(_inventory[i].Description);
}
}
}
public class ArrayStore : IStore
{
const int maxSize = 3;
Item[] _inventory;
public ListStore()
{
_inventory = new Item[maxSize];
_inventory[0] = new Item("Shoes");
_inventory[1] = new Item("Duct Tape");
_inventory[2] = new Item("Venti Cappuccino");
}
public void PrintInventory()
{
//Here's the problem...
//Duplicating code at all or with slight differences
//should make your refactoring senses tingle!
for (int i = 0; i = itemList.Count; i++)
{
Console.WriteLine(itemList[i].Description);
}
}
}
What's changing here is the particulars of the iteration. We'd like to encapsulate iteration and the Iterator pattern does just that by introducing an Iterator interface that will allow us to control how we loop through any collection in any way we want.
It's not too tricky so let's take a look.
interface IIterator
{
//This will tell us if there is a
//next element in a collection...
bool HasNext();
//And this will return the next element.
object Next();
}
//Let's see how these iterators are implemented.
//first for a List...
class ListIterator : IIterator
{
List<Item> _items;
//_position keeps track of where we
//are in the iteration of the list.
int _position = 0;
public ListIterator(List<Item> items)
{
_items = items;
}
public object Next()
{
Item item = _items[_position];
_position++;
return item;
}
public bool HasNext()
{
if (_position >= items.Count)
{
return false;
}
else
{
return true;
}
}
}
//And now for the array...
class ArrayIterator : IIterator
{
Item[] _items;
int _position = 0;
public ListIterator(Item[] items)
{
_items = items;
}
public object Next()
{
Item item = _items[_position];
//Increment our position...
_position++;
return item;
}
public bool HasNext()
{
if (_position >= items.Length)
{
return false;
}
else
{
return true;
}
}
}
Now we need to make some small changes to the Store interface...
interface IStore
{
//All we need is a method to retrieve
//the appropriate iterator.
IIterator GetIterator();
}
public class ListStore : IStore
{
List<Item> _inventory;
public ListStore()
{
_inventory.Add(new Item("Soap"));
_inventory.Add(new Item("Fan"));
_inventory.Add(new Item("Fish Tank"));
}
public void GetIterator()
{
//this iterator is passed a list item
//to loop through.
return new ListIterator(_list);
}
}
public class ArrayStore : IStore
{
const int maxSize = 3;
Item[] _inventory;
public ListStore()
{
_inventory = new Item[maxSize];
_inventory[0] = new Item("Shoes");
_inventory[1] = new Item("Duct Tape");
_inventory[2] = new Item("Venti Cappuccino");
}
public void GetIterator()
{
return new ArrayIterator(_inventory);
}
}
Now we can loop through each element in a collection regardless of the type, as long as the class associated with the collection returns the correct Iterator type.
Let's see this in action.
class Program
{
static void Main(string[] args)
{
IStore listStore = new ListStore();
IStore arrayStore = new ArrayStore();
IIterator listStoreIterator = listStore.GetIterator();
IIterator arrayStoreIterator = arrayStore.GetIterator();
//Now it doesn't matter what type of
//collection our stores use in their implementation
//as long as they know their iterator!
PrintInventory(listStoreIterator);
PrintInventory(arrayStoreIterator);
}
//we could have also kept this in the store class and
//done the iteration internally.
void PrintInventory(IIterator iterator)
{
while (iterator.HasNext())
{
Item storeItem = (Item)iterator.Next();
Console.WriteLine(storeItem.Description);
}
}
}
This is, as I may have mentioned before, a very simplified example with a simple version of the pattern and is not the only way to implement this or any pattern. Just for one example we could have used generics in the iterator's Next() method to return an exact type instead of an object type.
We could iterate through a collection in any way as well: backwards, skipping every other one or anything, really.
This is basically what c# uses when you use a foreach loop, except they use an IEnumerator interface instead of our Iterator and the IEnumerable interface for returning the appropriate iterator or enumerator, ie. the GetIterator() method in our IStore interface. The actual 'foreach' keyword is
syntactic sugar for making the code easier to read or write but is basically using something like our while loop.
In actual c# programming you'll never actually need to make an iterator from scratch since there's such a nice one built in with such simple syntax, but I thought it might be illuminating to see how it's actually done.
Because this is an article about design patterns and not about all the neat features of c# I won't go into how to extend IEnumerable or IEnumerator here but here are some links for doing just that.
Further reading:
MSDN.com
codebetter.com
this article shows us how to code the same behavior
without using a seperate iterator interface, the iteration is done in the collection class using the
yield keyword.
No comments :
Post a Comment