The elusive IArray(T) interface: if nobody else is going to do it, I will…

So, I’ve been doing some work on and off on my own .NET library, Tao.NET. (I originally had it called just Tao, because I thought that was a cool name… plus, it’s you know, my name… but then it turned out that Tao is already freaking taken!). I actually think there’s a lot of pretty useful stuff in there. But what I haven’t gotten around to doing yet is actually publishing it with any sort of fanfare. It’s just sitting out there in Bitbucket land, silently, humbly.

What I want to do right now is talk about one of the classes in Tao.NET that I think is pretty useful. In the future maybe I’ll talk about some other classes—heck, maybe I’ll even write documentation some day!—but right now, today, all I want to talk about is the unassumuing IArray<T> interface.

About a hundred years ago, I asked on Stack Overflow why there is no IArray<T> interface in .NET. At the time I was mainly frustrated by the fact that there’s no interface in the BCL for exposing read-only collections with random access by index. (There’s ReadOnlyCollection<T>, sure; but that commits you to a specific implementation, which kinda goes against that whole, you know, “OOP” thing. Plus, ReadOnlyCollection<T> just flat-out doesn’t make a nice-looking API, as has been complained about before. That’s right I said it.)

Since asking the above question, and as .NET 4.0 has become more and more common, another very compelling justification for such an interface has occurred to me: the IList<T> interface is not covariant—nor can it be, since it is not a read-only interface. But an IArray<T> interface could totally be covariant. And that would be extremely useful.

To understand why (in case it isn’t obvious), consider the fact that arrays exhibit variance in .NET. This means you can do this:

string[] strings = new[] { "Burt", "Ernie" };

// Can't do this with IList<string> and IList<object>, now can you?
object[] objects = (object[])strings;

When you think about it, this really shouldn’t be legal. After all, T[] arrays are not read-only any more than an IList<T> is; they have indexed setters! Which means that the designers of the CLI (I think it was them?) determined that arrays should be variant despite the fact that this is just flat-out dangerous.

I mean, look:

// Will this work? Goodness, no! That's really a string[] array!
objects[0] = DateTime.Now;

What I’m getting at is that, if the CLI designers felt this was a good call, they must have had a darn good reason. And I think they did: type variance is useful, plain and simple.

Personally my theory is that half the time you write a method that accepts an IList<T>, you really have no need whatsoever for any of the interface’s invariant features: Add, Insert, Clear etc. All you really want is indexing. That’s it!

So, back to this Tao.NET library I was talking about—this is where the IArray<T> interface enters the picture. It’s actually really simple—like, ridiculously so. Here’s all you need:

// Notice: it's covariant!
public interface IArray<out T> : IEnumerable<T>
{
    int Count { get; }
    T this[int index] { get; }
}

Check that out—pretty straightforward, right? But now consider the ramifications:

  • Any time you write a method that accepts an IArray<T>, you can take an IArray<U> where U inherits from T. Can’t say that about IList<T>, now can you?
  • Having this interface suddenly makes some pretty sweet extension methods possible.

Here’s an example of such an extension method:

public static IArray<TResult> SelectIndexed<T, TResult>(this IList<T> source, Func<T, TResult> selector);

What does the above extension do? Just think of your plain vanilla Select method from System.Linq; only now the object returned provides indexing. (And no, it doesn’t need to cache an entirely new collection; just think about what the IArray<T> interface exposes and it should become clear that caching isn’t necessary.) This allows you to do this, for example:

string[] strings = new[] { "A", "BB", "CCC" };
IArray<int> lengths = strings.SelectIndexed(s => s.Length);
Console.WriteLine(lengths[1]);

The above code will output 2. Pretty neat, right? And that’s just an example. So far I’ve only added a couple of extension methods to Tao.NET that leverage the IArray<T> interface (in the TaoNet.Linq namespace), but more are on the way, including some really sweet ones like an OrderByIndexed, for example, or even a BinarySearch (consider this: once you have a SelectIndexed, you can binary search on not just the values in a list or array but any property of those values).

So yeah, personally I think this fills a pretty big gaping hole that’s been in the BCL for a while. Why not give it a try and let me know what you think?

Final note: I am not in any way tied to the name “Tao.NET”; if anything I’d probably like to change it as it’s a bit self-aggrandizing (not that I have anything against that, of course). So if anyone has any suggestions for better names, I’m all ears.

Leave a comment