Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are collection initializers on re-assignments not allowed?

I always thought it worked fine both ways. Then did this test and realized it's not allowed on re-assignments:

int[] a = {0, 2, 4, 6, 8};

works fine but not:

int [ ] a;
a = { 0, 2, 4, 6, 8 };

Any technical reason for this? I thought I would ask about it here, because this behavior was what I expected intuitively.

like image 759
Joan Venge Avatar asked Dec 23 '11 22:12

Joan Venge


1 Answers

First off, let's get the terms correct. That's not a collection initializer. That's an array initializer. A collection initializer always follows a constructor for a collection type. An array initializer is only legal in a local or field declaration initializer, or in an array creation expression.

You are completely correct to note that this is an odd rule. Let me characterize its weirdness precisely:

Suppose you have a method M that takes an array of ints. All these are legal:

int[] x = new[] { 10, 20, 30 };
int[] y = new int[] { 10, 20, 30 };
int[] z = new int[3] { 10, 20, 30 };
M(new[] { 10, 20, 30 });
M(new int[] { 10, 20, 30 });
M(new int[3] { 10, 20, 30 });

But

int[] q = {10, 20, 30}; // legal!
M( { 10, 20, 30 } ); // illegal!

It seems like either the "lone" array initializer ought to be legal everywhere that the "decorated" one is, or nowhere. It's weird that there is this pseudo-expression that is valid only in an initializer, not anywhere else that an expression is legal.

Before I both criticize and defend this choice, I want to say that first and foremost, this discrepancy is a historical accident. There's no compellingly good reason for it. If we could get rid of it without breaking code, we would. But we can't. Were we designing C# from scratch again today I think odds are good that the "lone" array initializer without "new" would not be a valid syntax.

So, let me first give some reasons why array initializers should NOT be allowed as expressions and should be allowed in local variable initializers. Then I'll give some reasons for the opposite.

Reasons why array initializers should not be allowed as expressions:

Array initializers violate the nice property that { always means introduction of a new block of code. The error-recovery parser in the IDE that parses as you are typing likes to use braces as a convenient way to tell when a statement is incomplete; if you see:

if (x == M(
{ 
   Q(

Then it is pretty easy for the code editor to guess that you are missing )) before the {. the editor will assume that Q( is the beginning of a statement and it is missing its end.

But if array initializers are legal expressions then it could be that what is missing is )})){} following the Q.

Second, array initializers as expressions violate the nice principle that all heap allocations have "new" in them somewhere.

Reasons why array initializers should be allowed in field and local initializers:

Remember that array initializers were added to the language in v1.0, before implicitly typed locals, anonymous types, or type inference on arrays. Back in the day we did not have the pleasant "new[] { 10, 20, 30}" syntax, so without array initializers you'd have to say:

int[] x = new int[] { 10, 20, 30 };

which seems very redundant! I can see why they wanted to get that "new int[]" out of there.

When you say

int[] x = { 10, 20, 30 };

it is not syntactically ambiguous; the parser knows that this is an array initializer and not the beginning of a code block (unlike the case I mentioned above.) Nor is it type-ambiguous; it is clear that the initializer is an array of ints from the context.

So that argument justifies why in C# 1.0 array initializers were allowed in local and field initializers but not in expression contexts.

But that's not the world we're in today. Were we designing this from scratch today we probably would not have array initializers that do not have "new". Nowadays of course we realize that the better solution is:

var x = new[] { 10, 20, 30 };

and that expression is valid in any context. You can explicitly type it on either the "declaration" side or the "initializer" side of the = if you see fit, or you can let the compiler infer the types of either side or both.

So, summing up, yes, you are right that it is inconsistent that array initializers can be only in local and field declarations but not in expression contexts. There was a good reason for that ten years ago, but in the modern world with type inference, there's no longer much of a good reason for it. It's just a historical accident at this point.

like image 94
Eric Lippert Avatar answered Sep 21 '22 00:09

Eric Lippert