C# Generic Covariance

Intuitively, an immutable, generic collection of a subclass should be covariant with a collection of the superclass. Here’s an example:

interface IWidget
class Widget : IWidget
interface IWidgetCollection : IEnumerable<IWidget>
class WidgetCollection : List<Widget>

As I think most experienced developers will recognize, this is not a contrived example (aside from the “Widget” part) and is something that would be extremely useful. I am repeatedly surprised when I receive the compiler error that you cannot convert WidgetCollection to IEnumerable<IWidget>. List implements IEnumerable so WidgetCollection implements IEnumerable<Widget>. Thus, the crux of the matter is: should IEnumerable<Widget> be covariant with IEnumerable<IWidget> given that Widget : IWidget? I say yes and I’ve yet to see a good reason as to why not.

There’s actually a very good reason why List<Widget> is not covariant with List<IWidget>. In a nutshell, covariance of non-immutable collections would potentially allow insertion of a different subclass into a collection of another subclass through upcasting. See this for full-blown details. However, this scenario does not apply to IEnumerable nor immutable collections.

I’ve encountered this very sticky problem twice in the real world. Most recently, I was trying to use generic collections of an interface in combination with generic collections of classes that implemented the interface to implement the strategy pattern in way that didn’t require downcasting.

List<A> aic = _retreiver.Get(max);
foreach (A ai in aic)
{
    _updater.UpdateRemote(ai);
    _updater.UpdateLocal(ai);
}
_updateCompletionRecorder.RecordCompletion(aic);

_retreiver, _updater, and _updateCompletionRecorder are strategies and A is a generic type variable. In terms of the intro example, I needed _updateCompletionRecorder to be able to work with Widgets instead of IWidgets without downcasting in the implementation.

In addition, the project I was working on uses remoting. So I needed to declare non-generic types (which I feel is good practice anyway) for my collections since you cannot serialize a directly generic collection, EG WidgetCollection (serializable) instead of List<Widget> (not serializable). In this case, I was utlimately able to leave List<> in to solve the problem since I ended up not needing to cross a remoting boundary in this case. However, I wasted quite a bit of time and ended up with an inferior design, IMO.

Everthing would have been much better if IEnumerable<Widget> had been covariant with IEnumerable<IWidget>.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: