I was reading over at devlicio.us this morning and caught Derik Whittaker's article on Creating and Using Custom Collection Enumerators. After reading it, I was left with a funny feeling that we may be able to pull all that off with C# 2.0 Iterators and the Yield Keyword.
Often I avoid unnecessarily letting the client know about the details of an inner child collection class if I can get away with it, so it is not unusual for me to write something like this if it "makes sense":
public class Order : IEnumerable<OrderLine>
{
private List<OrderLine> _lineItems;
public Order()
{
_lineItems = new List<OrderLine>();
_lineItems.Add(new OrderLine("Code1", 5));
_lineItems.Add(new OrderLine("Code2", 10));
_lineItems.Add(new OrderLine("Code3", 15));
_lineItems.Add(new OrderLine("Code4", 20));
_lineItems.Add(new OrderLine("Code5", 25));
_lineItems.Add(new OrderLine("Code6", 30));
}
#region IEnumerable<OrderLine> Members
public IEnumerator<OrderLine> GetEnumerator()
{
foreach (OrderLine lineItem in _lineItems)
{
yield return lineItem;
}
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
foreach (OrderLine lineItem in _lineItems)
{
yield return lineItem;
}
}
#endregion
}
By having the Order Class implement IEnumerable<OrderLine>, I now can enumerate through the line items of the order without handing over my precious _lineItems Class:
Order order = new Order();
foreach (OrderLine lineItem in order)
Console.WriteLine(lineItem);
Console.ReadLine();
Obviously there are other ways to pull this off, like just having a property that returns IEnumerable<OrderLine> instead of List<OrderLine>:
public IEnumerable<OrderLine> LineItems
{
get
{
return _lineItems;
}
}
And now you would iterate as such:
Order order = new Order();
foreach (OrderLine lineItem in order.LineItems)
Console.WriteLine(lineItem);
Console.ReadLine();
The key, however, in the first example is that yield keyword, which allows us to avoid all the code you can find in Derik's post. It has all the smarts so we don't have to explicitly implement the actual members of IEnumerable<T>, IEnumerable, etc. (Current, MoveNext(), Reset(), etc.).
If we want to create a custom enumerator that perhaps only returns line items with a quantity greater than or equal to x units, we can add the following to the Order Class, which again avoids all the extra coding:
public IEnumerator<OrderLine> GetUnitsEnumerator(int minimumUnits)
{
foreach (OrderLine lineItem in _lineItems)
{
if (lineItem.Quantity >= minimumUnits)
yield return lineItem;
}
}
Now I use the code as such and only get the line items with 20 or more units.
IEnumerator<OrderLine> enumerator = order.GetUnitsEnumerator(20);
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
Console.ReadLine();
Of course, if you could really care less about exposing the inner child collection _lineItems to the world by using the following property:
public List<OrderLine> LineItems
{
get
{
return _lineItems;
}
}
Then you have the option of just grabbing all the items with 20 or more units as such:
Order order = new Order();
List<OrderLine> lineItems = order.LineItems.FindAll(
delegate(OrderLine lineItem)
{
return (lineItem.Quantity >= 20);
}
);
foreach (OrderLine lineItem in lineItems)
Console.WriteLine(lineItem);
Console.ReadLine();
There may be better ways to pull this off, but this can get you thinking about custom collection enumerators, etc., without diving too deep into implementing the interfaces and writing as much code. I need to spend much more time with this myself as I am sure this is just scratching the surface.
Btw, here is the OrderLine class for sake of completeness:
public class OrderLine
{
private string _code;
public string Code
{
get
{
return _code;
}
set
{
_code = value;
}
}
private int _quantity;
public int Quantity
{
get
{
return _quantity;
}
set
{
_quantity = value;
}
}
public OrderLine(string code, int quantity)
{
_code = code;
_quantity = quantity;
}
public override string ToString()
{
return string.Format("{0}: {1} units", _code, _quantity.ToString());
}
}
Posted
Thu, Oct 5 2006 8:43 AM
by
David Hayden