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?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
vlad
  • 4,748
  • 2
  • 30
  • 36

2 Answers2

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]);
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 3
    Makes sense, but I'm still curious _why_ I can't have a base class type constraint of `Array`... – vlad Feb 10 '13 at 05:35
  • Also, out of curiosity, is that different from using a parameter of type `Array`? Is there some boxing going on if I use `Array`? – vlad Feb 10 '13 at 05:36
  • 1
    @vlad 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. – Servy Feb 10 '13 at 05:38
  • 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? – vlad Feb 10 '13 at 05:43
  • 1
    @vlad 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. – Servy Feb 10 '13 at 05:46
  • what if you wanted your constraints to be say double[] or double[,] – mike01010 Dec 12 '14 at 01:58
  • @mike01010 Then you don't use generics and you create two overloads. – Servy Dec 12 '14 at 04:32
-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.

IS4
  • 11,945
  • 2
  • 47
  • 86
  • 3
    It wouldn't be quite useless. Were it not for the prohibition, one could write a `CopyAndReverseArraySegment(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. – supercat Apr 30 '15 at 15:51