Variance is cool, but you’ve gotta get your hands a bit dirty

I feel like I’ve kinda sorta written about this before, but noticing some recent questions on Stack Overflow about “casting” lists, dictionaries, etc. has convinced me that it just made sense to go ahead and put some code out there to save a lot of developers from serious headache.

So, I created a .NET library called VariantCollections and pushed it to GitHub.

In the spirit of DRY, rather than reiterate what I’ve already included in the library’s README file, I will simply include the contents of that file below, for ease of access. For future reference, however, I advise reading the README directly on GitHub as I’m likely to update it in the future.

VariantCollections

The basic idea of this library is very simple. Often a .NET developer will have something like an
IList<string> (for example) and will want to “cast” it to an IList<object>. Unfortunately, he
or she will quickly discover that this is not possible because the IList<T> interface is not
covariant.

The reason for this, of course, is that if IList<T> were covariant, then code like this would
be possible:

var list = new List<int> { 1, 2, 3 };
var objectList = (IList<object>)list;
objectList.Add("Oh no! I'm adding a string to a List<int>!");

What’s frustrating about this reason for the IList<T> type’s invariance is that we are not
always interested in those features of IList<T> that make it invariant
. In particular, we are
often only really interested in features of IList<T> which could very easily be considered variant: that is,
treating the IList<T> interface as something much more basic: anything that guarantees random
read access by index.

The VariantCollections library addresses this in an extremely straightforward way: first, by
defining an interface which offers some of the features of IList<T> and is covariant:

public interface IListReader<out T> : IEnumerable<T>
{
	T this[int index] { get; }
	int Count { get; }
}

Next, by defining an extension method on any IList<T> which creates an
IListReader<T> for that list:

public static IListReader<T> AsVariant<T>(this IList<T> list);

What this allows us to do is achieve exactly the kind of variance we would like for IList<T> to
have, while avoiding the illegal parts (Add, Insert, Remove, etc.):

var list = new List<int> { 1, 2, 3 };
IListReader<int> reader = list.AsVariant();
IListReader<object> objectReader = reader; // This is legal!

Loyal readers may recall that this is remarkably similar (basically identical, in fact) to the IArray<T> concept I wrote about a while back. The reason I decided to create a separate library for this and go with the name IListReader<T> is that I realized the fundamental reason no collection interface in the BCL is covariant (or contravariant, for that matter) is that they all offer reading and writing functionality. By separating the reading operations from the writing operations (via –Reader and –Writer interfaces, naturally!), we can actually offer covariant and contravariant equivalents of basically all of the collection classes in the BCL!

Sounds pretty promising, don’t you agree? Granted, I haven’t actually done it yet. But I did implement, in addition to IListReader<T>, a reader for what I felt (and I’m pretty confident this is true) is the second most popular candidate for .NET developer’s attempted casting shenanigans: IDictionary<TKey, TValue>. How so? Pretty much exactly the same way, with an IDictionaryReader<TKey, TValue> interface:

public interface IDictionaryReader<in TKey, out TValue>
{
	TValue this[TKey key] { get; }
	int Count { get; }
	bool ContainsKey(TKey key);
}

Note that this interface is covariant in terms of its values, but contravariant in terms of its keys. What this means is that we can write code like this:

var dict = new Dictionary<object, string>
{
    { 1, "One" },
    { "2", "Two" },
    { new object(), "Three" }
};
IDictionaryReader<object, string> reader = dict.AsVariant();
IDictionaryReader<string, object> objectReader = reader; // This is not a typo!
Console.WriteLine(objectReader.ContainsKey("1")); // outputs 'False'
Console.WriteLine(objectReader["2"]); // outputs 'Two'

Pretty neat, huh? Stay tuned; I’ll flesh this one out over the next couple of days.

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: