… and ended (as always) with code

Our requirements from an index-type are fairly minimal…

public interface IIndex
{
	int Value { get; }
}

… and this would be enough for a number of scenarios, although not one particular scenario that I wanted: being able to construct a typed-index from an int. This operation is useful for being able to expose existing properties of an array as their typed equivalent, in particular I wanted to expose the length of the array in each dimension.

public interface IIndex<TIndex> : IIndex
	where TIndex : struct, IIndex<TIndex>
{
        TIndex WithValue(int value);
}

The above is then used in conjunction with the fact that T : struct implies T : new() to essentially gain a T : new(int) constraint by using var t = new T().WithValue(i). I could call this a tweak on the prototype-pattern but at the end of the day it’s a bit of a hack around the type constraints I’m able to achieve with generics.

Due to index types needing to be structs we can’t use inheritance, making implementing an index type a rather tedious and unpleasant copy-paste affair. While this obviously has plenty of disadvantages, it has the benefit that each index type is free to enabled – or not – methods and operations as it sees fit, for example IComparable<>, IEquatable<>, operator overloads, &c. No more than five or six index types will be created, with some having the requirement to be comparable (e.g. yearA < yearB) and others with the requirement to explicitly not be comparable (e.g. simulation – each is fully independent of all others) so I consider the trade-offs being made here to be acceptable.

Implementing the wrappers for arrays was also a rather tedious job as each rank requires a separate implementation with the correct number of type parameters.

public class StrongArray<TValue, TIndex0, TIndex1> : StrongArray
	where TIndex0 : IIndex<TIndex0>
	where TIndex1 : IIndex<TIndex1>
{
	private readonly TValue[,] array;

	public StrongArray(TIndex0 length0, TIndex1 length1)
		: this(new TValue[length0.Value, length1.Value])
	{
	}

	public StrongArray(TValue[,] array)
		: base(array)
	{
		this.array = array;
	}

	public TValue this[TIndex0 index0, TIndex1 index1]
	{
		get { return array[index0.Value, index1.Value]; }
		set { array[index0.Value, index1.Value] = value; }
	}

        public TIndex0 Length0
        {
            get { return new TIndex0().WithValue(array.GetLength(0)); }
        }

        public TIndex1 Length1
        {
            get { return new TIndex1().WithValue(array.GetLength(1)); }
        }

	public TValue[,] Array
	{
		get { return array; }
	}
}

And, no, I couldn’t think of a better name at the time!

Was it worth it?

Definitely.
For about a day an a half of coding, what Code Metrics tells me is 125 lines of code – with the biggest contributor being a class of helper methods such as Enumerable.Range equivalents – and a very modest performance penalty, this thin abstraction over arrays has had a significant impact on how much information is conveyed by our code and has helped to reduce the amount of time and effort required to understand a piece of code.

Leave a Reply