Tuesday, November 27, 2007

IEnumerable, not just for simple collections

In C# IEnumerable is what lets you say foreach( string s in someListOfStrings ) {}
In C# 2.0 you can create objects that can be enumerated over very easily using the very Ruby-like "yield return" keyword. Here's a dumb example:

private List strings;
public IEnumerable Strings
{
get
{
if ( strings == null ) yield break;
else
foreach( string s in strings )
yield return s;
}
}

The compiler will take care of all the magic of managing the state of your loops or recursion or whatever.

Pretend that the code above was in a class called ExampleStrings. I could write this code:
foreach( string s in exampleStrings.Strings ) {}


Like I said, that was a dumb example, but now you've seen an example of how easy it is to make something Enumerable. And because of how easy it is, I'm a big IEnumerable fan. I've found two cases where I really like to use it:
  • When a library needs to return a bunch of "stuff" but doesn't know exactly how the consumer wants to use it
  • When some non-trivial work needs to be performed to determine what the next element to be returned should be
Take the first one first. Suppose you're writing a library which needs to return the name of all the scanners attached to the computer. You could write this to return a List, but what if the consumer needs a DataTable? You'll build your list and return it, then the consumer will loop through your list and build a DataTable. If you return IEnumerable instead of List you make this O(n) instead of O(2n). You also delay whatever calls need to be made to find out what scanners are connected to when you actually start looping through them. That's because your IEnumerable code doesn't execute when the property is referenced. So in the following code a break point placed in the get of our Strings property wouldn't hit until the last line:
IEnumerable strings = exampleStrings.Strings;
if ( strings == null ) return;
List l = new List( strings );

So maybe O(n) vs O(2n) isn't that big of a deal. And the fact that you don't have to create a List object isn't too huge either. Its still nicer, cleaner, and totally easy to do though, so why not do it?

As usual, its the second bullet that really makes taking a look at IEnumerable worth while. I've used this pattern in two actual projects too. One I referenced in an early post, Fun With Circles. The other I've used when I was manually performing some data paging.

In fun with circles I used IEnumerable to abstract away the calculation that determined the Point where the next dot in my circle should go. That way I completely separated my drawing code from the math to determine where to draw.

In the data paging example I created an IEnumerable DataFetcher. All the consumer had to do was tell the DataFetcher where to start and what direction to go in (start, end, specific record ID and forward or backward) and then simply foreach through the resulting rows. When the consumer had enough rows to fill its page, it would simply break out of the loop. If the data source ran out of data first, then the loop would end on its own. With this in place the consumer was completely isolated from when the data was being retrieved and how it was being "stitched" together. This also made it incredibly easy to add new data fetching behavior to the DataFetcher class as needed. Then by simply setting a property on the class you could change its underlying behavior without the consumer updating any other lines of code.

Of course, IEnumerable isn't required for any of this. You can accomplish the same thing with a GetNext() method. IEnumerable just adds some very nice syntactic sugar to both the consumer's code and the source's code.

1 comment:

  1. hah i learned about this in the training class i took on 2.0. It is one of my favorite new things. Combine this with anonymous methods and you basically have the "code blocks" concept from ruby but strongly typed (which i could give or take).

    ReplyDelete

Note: Only a member of this blog may post a comment.