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

2

14

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?

Effluent answered 10/2, 2013 at 5:29 Comment(1)
So no actual answer so far.Gulden
R
23

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]);
    }
}
Riojas answered 10/2, 2013 at 5:31 Comment(7)
Makes sense, but I'm still curious why I can't have a base class type constraint of Array...Effluent
Also, out of curiosity, is that different from using a parameter of type Array? Is there some boxing going on if I use Array?Effluent
@Effluent There's no boxing since arrays are reference types, not value types, although you are boxing the objects you get out of it unlike with a typed array. It's also not the same since an Array could also be a 2, 3, or N dimentional array, or an array that's not 0 indexed.Riojas
Also makes sense. Just to be clear, you're saying that what I tried to write is simply not allowed by the compiler, because the correct syntax is what you posted? That is, everything that can be done with where T : Array can be done in the parameter list?Effluent
@Effluent You should either be using this syntax or just passing an array in a non-generic method, in the event that you do not know the dimension of the array being passed in or are dealing with a 1 (or any non-zero) indexed array.Riojas
what if you wanted your constraints to be say double[] or double[,]Cowpox
@Cowpox Then you don't use generics and you create two overloads.Riojas
L
-1

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.

Litotes answered 1/1, 2015 at 15:17 Comment(1)
It wouldn't be quite useless. Were it not for the prohibition, one could write a CopyAndReverseArraySegment<T>(T dest, T source, int start, int length) where T:System.Array and have it accept invocations where either or both of the source or destination was System.Array, but still reject invocations where source and dest were incompatible array types. As it is, I don't believe there is a way to allow System.Array as a particular type for one of the parameters without having any derivative of that type also be deemed acceptable.Spain

© 2022 - 2024 — McMap. All rights reserved.