Why are collection initializers on re-assignments not allowed?
Asked Answered
S

2

15

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.

Sutton answered 23/12, 2011 at 22:40 Comment(1)
You might find this helpful: #7351953 (not strictly a duplicate)Treulich
C
21

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.

Centrality answered 24/12, 2011 at 1:10 Comment(4)
Wow. StackOverflow would be 1000x more awesome if you posted an answer to every C# question.Abort
@JimSchubert: I appreciate the sentiment very much but I assure you it would be worse. I don't know the answer to 99% of the C# questions on the site! I am an expert on the history, design and implementation of the language; narrowly focussing on that for the last six years means that there are huge areas of practical C# programming that I know next to nothing about.Centrality
Awesome answer. For the benefit of the original poster, who might not know, Eric Lippert's answers are definitive :-)Solarize
@RossPatterson: Joan and I go way back.Centrality
B
1

It most be:

    int [] a;// ={1,2,3,4};
    a = new [] {1, 2, 3, 4};

In VB works the same way as declaration, easier xD

Dim a() As Integer '={1,2,3,4}
a = {1, 2, 3, 4}
Bethune answered 23/12, 2011 at 22:52 Comment(7)
He is asking why this restriction exists.Illyria
Thanks, adding new[] before, makes it work. But why this restriction exists when it could work without it (just like when you first assign it)Sutton
Well, I think it is just for language, because in VB it can be done, we should look at MSIL code generated what is the diference.Bethune
Yeah I upvoted because of the VB case which I didn't know but is useful to know.Sutton
Thanks Joan. @Bigfellahull: collection initializers are allowed on re-assignments, the code he wrote wasnt correct so it can answer his question, or deny the question becaus it is wrong.Bethune
I didn't downvote but int[] a = {0, 2, 4, 6, 8}; is correct. It compiles for direct assignments: msdn.microsoft.com/en-us/library/aa664573(v=vs.71).aspxSutton
Well, like the above link to duplicate says, it's more a decision without any specific reason aside that someone didn't like it.Skeg

© 2022 - 2024 — McMap. All rights reserved.