Why C# behaves differently on two int array syntaxes

C#Arrays

C# Problem Overview


Array in C# is co-variant implicitly on reference type:

object[] listString = new string[] { "string1", "string2" };

But not on value type, so if you change string to int, you will get compiled error:

object[] listInt = new int[] {0, 1}; // compile error

Now, the concern is when you declare int array like two syntaxes below which do not explicitly declare the type int, just only differentiate on new[], compiler will treat differently:

object[] list1 = { 0, 1 };       //compile successfully
object[] list2 = new[] {0, 1};    //compile error

You will get object[] list1 = { 0, 1 }; compiled successfully, but object[] list2= new[] {0, 1}; compiled error.

It seems the C# compiler treats

object[] list1 = { 0, 1 };

as

object[] list1 = new object[]{ 0, 1 };

but

object[] list2 = new[] { 0, 1 };

as

object[] list2 = new int[]{ 0, 1 };  //error because of co-variant

Why C# compiler behaves in the different way on this case?

C# Solutions


Solution 1 - C#

The version that compiles uses an array initializer to initialize list1. The C# language spec, §1.110 ("Array initializers") states:

> An array initializer consists of a sequence of variable initializers, > enclosed by “{”and “}” tokens and separated by “,” tokens. Each > variable initializer is an expression or, in the case of a > multi-dimensional array, a nested array initializer. > > The context in > which an array initializer is used determines the type of the array > being initialized. In an array creation expression, the array type > immediately precedes the initializer, or is inferred from the > expressions in the array initializer. In a field or variable > declaration, the array type is the type of the field or variable being > declared. > > When an array initializer is used in a field or variable > declaration, such as: > > int[] a = {0, 2, 4, 6, 8}; > > it is simply shorthand for an equivalent array creation expression: > > int[] a = new int[] {0, 2, 4, 6, 8};

So it is obvious that this should compile.

The second version uses an explicit array creation expression, where you instruct the compiler specifically what type of array to create. §1.51.10.4 ("Array creation expressions") states:

> An array creation expression of the third form is referred to as an > implicitly typed array creation expression. It is similar to the > second form, except that the element type of the array is not > explicitly given, but determined as the best common type (§1.50.2.14) > of the set of expressions in the array initializer.

Therefore, the second version is equivalent to

object[] list2 = new int[] { 0, 1 };

So the question now effectively becomes "why can I not assign an int[] to an object[]", just as you mention at the end of the question. And the answer is also simple, given in §1.109 ("Array covariance"):

> Array covariance specifically does not extend to arrays of > value-types. For example, no conversion exists that permits an int[] > to be treated as an object[].

Solution 2 - C#

The declaration

object[] listInt = new int[] {0, 1};

is invalid because covariant array conversions are not allowed for value types (and int is a value type). Alternatively, the declaration

object[] listInt = new string[] {"0", "1"};

is valid because covariant array conversions are allowed for reference types. This is because the assignment x = (object)myString only involves a simple assignment, but y = (object)myInt requires a boxing operation.

Now on to the difference between the two declarations. In the declaration object[] list2 = new[] { 0, 1 }, due to how type inference works it first looks at the Right Hand Side expression and concludes that new[] { 0, 1 } should be treated as new int[] { 0, 1 }. Then it tries to assign this int array to an object array, giving an error because of the covariant conversion of value types issue. The declaration object[] list1 = { 0, 1 }, though, uses a collection initializer, and in those circumstances the type of the collection is where the type is defined, so each element will instead be cast to the type expected by the collection.

Solution 3 - C#

When you are using { and }, you use collection initializers (see: http://msdn.microsoft.com/en-us/library/vstudio/bb384062.aspx). The values between those brackets will have to be put somewhere. Therefor a collection has to be created. The compiler will anaylize the context to find out what kind of collection.

In case the first: object[] list1 = { 0, 1 }; it is clear there should be a collection created. But what kind should it be? There is no new operation somewhere. There is only one hint: list1 is of type object[]. So the compiler creates that collection and fills it with the valiues.

In your second example object[] list1 = new[] { 0, 1 }; there is another hint: new[]. And this hint explicitly says: There is going to be an array. That array does not have a type, so it will try to find the type of the array by anaylizing the values. These are all int's so it will create an array of int's and fills it. The other hint object[] is totally ignored because hints of creation are much more important than hints where it should be assigned to. Now the compiler wants to assign this array to list1 and BOOM: that does not fit!

Solution 4 - C#

The statement object[] list1 = { 0, 1 }; compiles because the compiler is smart enough to know you are attempting to convert an array of numeric types to a reference-type array, so it boxes the Int32 elements into reference types.

You could also explicitly box the primitive type:

object[] list2 = Array.ConvertAll<int, Object>(new[] { 0, 1 }, input => (Object)input);

The compiler will not implicitly do the boxing for you when you have specified 'int[]' or 'Int32[]' as the array type, but it seems like this could be added to C#.

Solution 5 - C#

An array initialiser is a compiler convenience. If I say "I am declaring an array of objects and assigning it a value," it is reasonable for the compiler to assume that your { 0, 1 } is an object array and interpret it as such. Although the syntax appears to be an assignment, it is not: you're using an initialiser. The longhand for this syntax is object[] list1 = new object[] { 0, 1 }

When you say new[] { 0, 1 }, this is an expression that creates an array and initialises it. This expression is evaluated independently of what you're assigning it to - and because the compiler detects the implicit integer typing, it creates an int[]. The longhand version of that expression is object[] list2 = new int[] { 0, 1 }

If you compare the longhand versions of these two statements, it's clear to see where they differ.

Solution 6 - C#

object[] listInt = new int[] {0, 1};

is shorthand for

object[] listInt;
listInt = new int[] {0, 1};

which doesn't work because int[] is not covariant with object[].

And when you say new[], it is equivalent to new int[], hence the same applies.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncuongleView Question on Stackoverflow
Solution 1 - C#JonView Answer on Stackoverflow
Solution 2 - C#erikkallenView Answer on Stackoverflow
Solution 3 - C#Martin MulderView Answer on Stackoverflow
Solution 4 - C#GM LucidView Answer on Stackoverflow
Solution 5 - C#Dan PuzeyView Answer on Stackoverflow
Solution 6 - C#user541686View Answer on Stackoverflow