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?
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[].
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.
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!
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#.
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.
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.
Related
This question already has answers here:
Can't use an "inline" array in C#?
(3 answers)
Closed 1 year ago.
I want to define a function that returns a vector (what is it called in C#?). I wrote:
static public int[] FunctionThatReturnsAVector(){
return {0, 0, 0, 0};
}
but it doesn't work. The compiler marks errors in random places. When I wrote:
static public int[] FunctionThatReturnsAVector(){
int[] result = {0, 0, 0, 0};
return result;
}
worked. Why my first idea didn't work, whereas the second one - does?
Why my first idea didn't work
Because it's a syntax error...
If you want array shorthand like that you need to use VB. The shortest it gets in C# is, I believe:
static public int[] FunctionThatReturnsAVector(){
return new[]{0, 0, 0, 0};
}
I should point out that this works because the method declares a return type of int[] and new[]{0,0,0,0} creates an int array because the compiler treats the 0 as ints for the type inference it performs.
If you had written:
static public long[] FunctionThatReturnsAVector(){
You would need to match the value types up of at least one item so that the compiler infers long[]
return new[]{0L, 0, 0, 0};
One item is a long and the other ints can be expanded to a long
whereas the second one - does?
The second one is a supported syntax of initing an array, the type for which is declared to the compiler on the left
Had you written :
var result = {0, 0, 0, 0};
You would be back to a syntax error.
You might be thinking "but why can't the compiler look at my first way and get the return type from the method, and see it's return, and infer that must be an init'r for an array of int, and just.. do it?
If you really want that shorthand, you'll have to ask for it to be implemented; the teams that write c# and the compiler are reachable on github.. https://github.com/dotnet/roslyn https://github.com/dotnet/csharplang
When writing my unit tests, I stumbled upon a problem: NUnit's [TestCaseAttribute], with the following constructor overloads:
public TestCaseAttribute(params object arg)
public TestCaseAttribute(params object args[])
would accept an array of integers, and a list of arguments including an array of strings, but would not accept an array of strings itself:
[TestCaseAttribute(new[] { 1, 2, 3 })] //works
[TestCaseAttribute("Other string", new[] { "1", "2", "3" })] //works
[TestCaseAttribute(new[] { "1", "2", "3" })] //compilation error?
This surprised me, so I verified the behavior:
private static void PrintTypes(params object[] objects)
{
Console.WriteLine("Array type is " + objects.GetType());
Console.WriteLine("Object count is " + objects.Length);
Console.WriteLine("Object type is " + objects[0].GetType());
}
public static void Main()
{
Console.WriteLine("Array of ints: ");
PrintTypes(new[] { 1, 2, 3 });
Console.WriteLine();
Console.WriteLine("Array of strings: ");
PrintTypes(new[] { "1", "2", "3" });
}
The output is somewhat baffling to me - it seems like the integer array is treated as a single object, but a string array is unrolled:
Array of ints:
Array type is System.Object[]
Object count is 1
Object type is System.Int32[]
Array of strings:
Array type is System.String[]
Object count is 3
Object type is System.String
And if we add the following method:
private static void PrintTypes(object obj)
{
Console.WriteLine("In object method");
Console.WriteLine("Object type is " + obj.GetType());
}
the compiler seems to prefer it for an array of ints, but not for an array of strings:
Array of ints:
In object method
Object type is System.Int32[]
Array of strings:
In array method
Array type is System.String[]
Object count is 3
Object type is System.String
Why is that the case? I'm assuming it has something to do with the integers not being reference types, but I'd like more of an explanation as to what exactly dictates that the compiler prefers different overloads for different arrays, and why one is allowable for the attribute constructor, but not the other.
You are on the right track with int not being a reference type being the reason for the observed behavior with your PrintTypes test method.
According to the C# specification (link), chapter 15.6.2.5
Parameter arrays:
A parameter array permits arguments to be specified in one of two ways
in a method invocation:
• The argument given for a parameter array can be a single expression that is implicitly convertible (§11.2) to the parameter array type. In this case, the parameter array acts precisely like a value parameter.
Chapter 11.2 (Implicit reference conversions) explains what "implicitly convertible" means with respect to arrays:
The implicit reference conversions are:
[...]
• From an array-type S with an element type SE to an array-type T with an element type TE, provided all of the following are true:
- S and T differ only in element type. In other words, S and
T have the same number of dimensions.
- An implicit reference conversion exists from SE to TE
Note that the existence of an implicit reference conversion is a requirement here. A conversion from a value type to a reference type is not an (implicit) reference conversion (see chapter 11.2 again). This is the reason why the the int[] array is just being treated as a single object argument, which leads to PrintTypes being called in expanded form.
Now, why then is
[TestCaseAttribute(new[] { 1, 2, 3 })] //works
compiling, while
[TestCaseAttribute(new[] { "1", "2", "3" })] //compilation error?
is not? Does it not contradict what i just said?
Lets take a look at the constructors of TestCaseAttribute:
public TestCaseAttribute(params object[] arguments);
public TestCaseAttribute(object arg);
public TestCaseAttribute(object arg1, object arg2);
public TestCaseAttribute(object arg1, object arg2, object arg3);
From the explanation given above, it should be clear that [TestCaseAttribute(new[] { 1, 2, 3 })] is compiling, because it uses the TestCaseAttribute(object arg) constructor overload.
new[] { "1", "2", "3" } is a string array, and the compiler choses as best matching overload for this argument TestCaseAttribute(params object args[]) (according to the rules explained in chapter 12.6.4 Overload resolution). Unfortunately, the overload resolution does not take the special requirement for attributes into account that attribute arguments have to be constant expressions. The rules for constant expressions (chapter 12.20 Constant expressions) state:
Note: Other conversions including boxing, unboxing, and implicit
reference conversions of non-null values are not permitted in constant
expressions.
(emphasis mine)
And this is what leads to the compiler error with regard to the attribute. The compiler chooses a constructor overload it thinks best fits the string[] argument. Then, at a later stage of the compilation process the compiler is trying to apply the attribute. It notices that a required implicit reference conversion is not permitted for attribute arguments, which results in the observed compile error.
As I was experimenting with params, I noticed the MS documentation says if you pass an array of int as a method parameter that has a signature of params object[] myParam, it will become a multi-dimensional array. But I noticed if you pass an array of objects[] or strings[] it does not. This seems like it would be a headache to work with, as you have to check for multi-dim arrays.
See MSDN link
Example:
public static void UsingParams(params object[] myParam)
{
//Code to return myParam
}
//myParam[0][0] = {1, 2}, multi-dimensional
int[] myIntArray = {1, 2};
UsingParams(myIntArray);
//myParam[0] = {"1", "2"}, single-dimensional
string[] myStrArray = {"1", "2"};
UsingParams(myStrArray);
Why does this occur?
Whenever you have a params parameter the compiler will attempt to accept an array representing all of the values for the params argument, if the parameter at the position in question is valid in that context. If it's not, then it tries to treat it as one item in the array of params values, rather than as the whole array. If it can't do that either then it will fail to compile.
In your second example a string[] can be implicitly converted to an object[], so it is passed as the entire list of parameters. This implicit conversion is valid because of array covariance.
In your first example the int[] cannot be implicitly converted to an object[] (array covariance is limited to reference types), so it is treated as one value in the array. An int[] can be implicitly converted to object, so what is passed is an object array containing an int[] as its only item. Note that an array with another array as an item is dramatically different from a multi-dimensional array.
C# is trying to figure out, when you only pass one value to a params argument, whether you mean for that value to be the array represented by the argument, or whether you're passing it the first argument of a larger array.
If you remove the params keyword, you'll see that int[] cannot be converted directly into an object[] (due to int being a non-reference type):
So C# figures it must just be the first of your params that you're passing in, rather than the entire array. It converts your code to this:
int[] myIntArray = {1, 2};
UsingParams(new object[]{myIntArray});
Basically your method signature is taking one or more objects and consolidating them into an array called myParam.
If multiple objects are passed individually, such as the call UsingParams(1, "hello", ...), they'll automatically be converted to the object[] array. This is a compiler trick / syntactic sugar.
If the object being passed is not an object[] array or list of individual objects, it will become the first argument of your array. In other words, if you pass an int[] then your myParam will be an array, the first element of which is an array also, making it a jagged array. This is because int[] is an object and the compiler isn't smart enough to figure out what you're doing and makes it the sole element of the object[] array. There is no implicit cast from int[] to object[] and this is why it doesn't happen.
The only time you can pass an array that will be populated as you'd expect is when that array type is object[], such as new object[] { 1, "hello", ... } or the array type is covariant. In this case, a string[] array is covariant and can be implicitly cast to an object[] while an int[] cannot.
In summary:
UsingParams(1, "hello") = good
UsingParams(new object[] { 1, "hello" }) = good
UsingParams(new string[] { "hi", "hello" }) = good (due to array covariance)
UsingParams(new int[] { 1, 2 }) = bad (no array covariance), will be a jagged array
Further reading on array covariance rules which also cites: "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[]."
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is this new[] a shorthand for?
Is there any difference between
var strings = new string[] { "hello", "world" };
and
var strings2 = new[] { "hello", "world" };
In this case, no difference, as new[] will infer the provided values type as string.
See Implicitly typed arrays.
No difference.
The second one is a syntactic-sugar called "Implicitly typed arrays", and both the expressions return an array of strings.
When you don't specify the type of the array, it is inferred from the types of the elements used to initialize the array.
To allow the inference, the expression must satisfy the following condition:
Considering an implicitly typed array expression like this:
var arr = new []{ obj1, ... , objn }
and the set of all the types of the elements in the initialization being:
S = {T1, ..., Tn}
to allow the inference (i.e. no compiler error) it must be possible for all the types { T1, ... , Tn } to be implicitly cast to one of the types in the set S.
So, for example, given the following classes:
class Base { }
class Derived1 : Base { }
class Derived2 : Base { }
class Derived3
{
public static implicit operator Base(Derived3 m)
{ return null; }
}
This code compiles:
var arr = new []{ new Derived1(), new Derived2(), new Derived3(), new Base()};
while the following does not:
var arr = new []{ new Derived1(), new Derived2(), new Derived3() };
since in the first case all the 3 types can be implicitly cast to type Base, and Base type is inside the set S = { Derived1, Derived2, Derived3, Base }, while in the second case all the types cannot be cast to one type in the set S = { Derived1, Derived2, Derived3 }
This feature has been introduced with C# 3.0 along with anonymous types and it makes instantiation of arrays of the latter easier.
For instance, this would be really hard to obtain without implicitly typed arrays:
var arrayOfAnonymous = new[] { new { A = 3, B = 4 }, new { A = 2, B = 5 } };
In this case, there is no difference. Because of hello and world are string;
var strings2 = new[] { "hello", "world" };
creates a string[] array which is the same with first one.
Second one is just called Implicitly Typed Arrays
If we go one step further, they have the same IL code.
None, the compile interprets it as new string[] { "hello", "world" };
It's just like using var, the compiler handles what you meant.
new[] creates an implicitly typed array in which the type is infered from the elements. while the other approach creates an array of string.
There is no difference.
In the 2nd case, the C# compiler is smart enough to infer the type of the array, since it sees that the values that are used to initialize the array, are of type string.
I can't seem to find any documentation on what new[] is supposed to be. From the example below it seems to be an object array shorthand
var json = new[] {
new object[] {"20-Jun-2008", 200 },
new object[] {"20-Jun-2009", 250 }
};
These are implicitly typed arrays.
See C# 3.0 specifications.
The syntax of array creation expressions (§7.5.10.2) is extended to
support implicitly typed array creation expressions:
array-creation-expression: ... new [ ] array-initializer
In an implicitly typed array creation expression, the type of the
array instance is inferred from the elements specified in the array
initializer. Specifically, the set formed by the types of the
expressions in the array initializer must contain exactly one type to
which each type in the set is implicitly convertible, and if that type
is not the null type, an array of that type is created. If exactly one
type cannot be inferred, or if the inferred type is the null type, a
compile-time error occurs.
The following are examples of implicitly typed array creation
expressions:
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[] { "hello", null, "world" }; // string[]
var d = new[] { 1, "one", 2, "two" }; // Error
The last expression causes a compile-time error because neither int
nor string is implicitly convertible to the other. An explicitly typed
array creation expression must be used in this case, for example
specifying the type to be object[]. Alternatively, one of the elements
can be cast to a common base type, which would then become the
inferred element type.
Implicitly typed array creation expressions can be combined with
anonymous object initializers to create anonymously typed data
structures. For example:
var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};
The notation is an implicitly typed array declaration.
In your case, it is a array of object arrays.
It means that new[] is an implicitly typed array. Since it's implicitly typed, you have to assign something to it as in this example. Just as you have to with the var keyword.
It's implicit typing. Since all the elements in that collection are object arrays, the compiler can deduce that the array itself must be a collection of object arrays.