One of the nice things that .NET 3.5 gives us is LINQ, which gives new life to the often-neglected IEnumerable generic interface. Sequence processing is now a first-class citizen in the C# world, and this is a good thing. However, it can be very tricky to design a usable API around enumerables. Today I present my solution to an annoying (but not showstopping) hurdle.
Consider the case where you have several types implementing an interface. In my case, these types all have a common ancestor, but this is beside the point. We’ll call the interface IFoo
, and the classes implementing this interface ThingOne
, ThingTwo
, and ThingThree
.
If I have a method that acts on a series of IFoo
objects, it is tempting to accept IEnumerable<IFoo>
as an argument. It makes sense, right? Well, sort of. Your users will not like this, because IEnumerable<ThingOne>
, IEnumerable<ThingTwo>
, and IEnumerable<ThingThree>
are not convertible to IEnumerable<IFoo>
. While it’s not too annoying, your users will have to cope by invoking Cast<IFoo>()
on their enumerables for them to work with your API. This not only adds code overhead (read: more code to maintain) but a minor amount of CPU and memory overhead to create an object that is going to cast objects to an interface that they explicitly implement.
The solution is rather simple, but not very obvious at first glance. Instead of using the signature void OperateOnFoos(IEnumerable<IFoo> foos)
, use this instead: void OperateOnFoos<T>(IEnumerable<T> foos) where T : IFoo
. It is a simple change, and the method will work exactly the same as before, except your users will no longer be required to cast their enumerables to IFoo
.
This technique applies just as well to situations where you take an enumerable to a class that is designed to be subclassed.
Now depending on how generics are implemented in your runtime of choice, you’re still probably going to see a small memory hit for each different T
you use when calling this method. But it’s not likely to be anywhere near the cost of creating a bunch of cast-enumerables that you really could do without. And that aside, the convenience of not having to Cast<IFoo>()
enumerables is totally worth changing one line of code.
.NET 4.0 will likely render this mechanism obsolete with the introduction of covariant interfaces, but in the meantime let’s all do something nice for our users!
Genius