Why can't System.Array be a type constraint?

2019-02-21 22:27发布

问题:

I'm working on a small project with a few different types of arrays (e.g. double[], float[], int[]. For verification / testing / sanity purposes, I'm printing out some of these arrays to the console as I go along. So I have multiple functions that look like these below (simplified for this example - assume I'm only dealing with single-dimension arrays):

void Print(float[] a) // prints an array of floats
{
    for (int i = 0; i < a.Length; i++)
    {
        Console.Write(a[i]);
    }
}

void Print(double[] a) // prints an array of doubles
{
    for (int i = 0; i < a.Length; i++)
    {
        Console.Write(a[i]);
    }
}

I, in my infinite wisdom, thought I could reduce some of the code duplication by simply creating a generic version of these functions. So I tried this:

void Print<T>(T t) where T : Array
{
    for (int i = 0; i < t.Length; i++)
    {
        Console.Write(t.GetValue(i));
    }
}

Intellisense isn't complaining, but the compiler fails with a very interesting error:

Constraint cannot be special class 'System.Array'

I've looked for an explanation (similar to Object or sealed classes, but haven't found much, besides a mention on msdn. Can anyone explain to me why this is the case? Why can't I specify a type constraint of System.Array?

p.s.: While typing this out, I realized that I can accomplish what I originally wanted more easily, with a simple function like this:

void Print(System.Array a)
{
    for (int i = 0; i < a.Length; i++)
    {
        Console.Write(a.GetValue(i));
    }
}

Is this why there's a special rule for arrays in the compiler?

回答1:

The appropriate syntax to do what you want is this:

void Print<T>(T[] array)
{
    for (int i = 0; i < array.Length; i++)
    {
        Console.Write(array[i]);
    }
}


回答2:

If taken the question literally, it would be useless to have an Array constraint. It's the same as it's useless to have a ValueType constraint, as it actually doesn't check whether you use a value type as a generic argument, but whether the type you are passing is assignable to ValueType.
So you can pass even Array as the generic argument and it's OK.

What is actually useful is to have an array contraint allowing any type that derives from Array, but not Array itself:

void Print<TArr>(TArr t) where TArr : array //or [*] or other fancy syntax

Where T can be [], [,], [,,], [,,,], and so on. The only over non-generic Array parameter is that we know the element type of the array.

Another way to solve this is to create a custom Array<T> class with implicit operator overloads of T[], T[,], T[,,] etc.

Edit:
There is no way to achieve this even in CIL (currently), because int[,] and Array don't differ in any interfaces or constructors. We need where T : Array but not Array itself contraint.