1

I saw NRE when using array initializer in object initializer and there was a update and https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6#extension-add-methods-in-collection-initializers but still I cannot understand.

var arr = new int[] { 1, 2, 3 } generate IL code using stelem. But int[] arr = { 1, 2, 3 } generate IL code using RuntimeHelpers.InitializeArray. I think it is using Array.Add extension method which was talk in that answer.

But in object initializer, all array initializer generate second code. An example,

new A() { 
    arr = new int[] { 1, 2, 3 }
}

Array arr is created using RuntimeHelpers.InitializingArray too. Then, doesn't it mean there isn't any problem in next code?

new A() {
    arr = { 1, 2, 3 } // Compiler error!
}

Not like old version of c# compiler, it makes compiler error saying system.array does not contain a definition for Add. What's happening?

EDIT I thought just syntax without new [] makes differences but actually more than three elements makes different IL code.

20chan
  • 167
  • 1
  • 10

2 Answers2

3

The second syntax ( { arr = {... } }) is syntax sugar for sequence of value.arr.Add(.) - presumably value.arr is not initialized in your constructor and hence NRE.

Assuming A is:

class A { 
     public int[] arr {get;set} 
}

then

var x = new A() {
    arr = { 1, 2, 3 } // Compiler error!
};

is the same as

var x = new A(); // note x.arr is default null here
x.arr.Add(1);  // NRE is arr is list, can't compile for int[]
x.arr.Add(2);
x.arr.Add(3);

Fix: use list, there is no reasonable way to add elements to an array. If using some other type that does not have .Add - implement extension method that is visible to that code.

Why version with new works:

var x = new A() {
    arr = new int[] { 1, 2, 3 } 
};

is equivalent* of

var x = new A();
x.arr = new int[] { 1, 2, 3 };

Note that in array initializer you can use both syntaxes to the same effect, but not in array field's initializers (All possible C# array initialization syntaxes)

int[] x = { 10, 20, 30 }; // valid, same as int[] x = new int[]{ 10, 20, 30 };

while new A { arr = { 10, 20, 30} } is not the same as new A { arr = new int[3]{ 10, 20, 30} }


*It is really a bit more complicated to satisfy rules when change must be observable - see Eric Lippert's comment below

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • But `new A() { arr = new [] { 1, 2, 3 } }` works. What's the differences? – 20chan Jan 26 '18 at 17:21
  • @phillyai: The value is being initialized and populated as an array. Without the new[], it's using the current state of arr and turning the sugar into series of .Add extension method calls. – Jonathon Chase Jan 26 '18 at 17:23
  • @JonathonChase But check this. https://sharplab.io/#v2:C4LglgNgPgAgDAAhgRgNwFgBQMDMSBMCAwggN5YKVJ4wAsCAsgBQCUZFVnAbgIYBOCfn0QBeBADsApgHcEAbQC6ZBMgA0CfOrwBfDJk6cw44IsF8+yBGNIr1mhDr2dtWbUA= It makes same result. – 20chan Jan 26 '18 at 17:26
  • @phillyai I tried to edit post addressing the difference between `new[]{}` and `{}`. Note that rules are different for array and fields of array type initializers. – Alexei Levenkov Jan 26 '18 at 17:47
  • 1
    I note that there is a small and unimportant error here. `x = new A { y = {1}};` is actually a sugar for `var t = new A(); t.y.Add(1); x = t;` The addition of `1` to `y` must happen *before* the assignment of the new `A` to `x` because those two mutations could be observed and we wish them to happen in the sensible order. When assigning a value, the assignment operator never does the side effect of the assignment until *after* the side effects of the *value assigned* are complete. – Eric Lippert Jan 26 '18 at 18:09
3

In an expression context these are legal array values:

new int[3] { 10, 20, 30 }
new int[] { 10, 20, 30 }
new[] { 10, 20, 30 }

In a local or member variable initializer context, this is a legal array initializer:

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

And this is a legal collection initializer:

List<int> x = new List<int> { 10, 20, 30 };

And, if X<T> is IEnumerable<T> and has a method Add(T, T, T), this is legal:

X<int> x = new X<int> { { 10, 20, 30}, {40, 50, 60} };

But in a member or collection initializer context this is not a legal array property initializer:

new A() {
    arr = { 10, 20, 30 }
}

(Note that I summarize and comment on the rules for arrays here https://stackoverflow.com/a/5678393/88656)

The question, as I understand it, is "why not?"

The answer is "no good reason". It's simply an oddity of the C# grammar and the rules for object and collection initializers.

I many times considered fixing this oddity but there was always something better to do with my time; it's a fix that benefits basically no one because the workaround is so easy.

I conjecture that there's nothing stopping the C# team from designing, specifying, implementing, testing and shipping that feature aside from the fact that there are still about a million other features that would be a better use of their time.

If you feel strongly about it, well, the compiler is open source; feel free to propose the feature and advocate for it. Or, for that matter, implement it and submit a pull request. (After you propose the feature.)

Until the feature you want is implemented you'll just have to use one of the three "expression" forms listed above. It is not burdensome to do so.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Why is the `x = {1, 2 ,3}` syntax allowed in member/collection initializer context if it doesn't also initialize a null, say, List property? You end up getting hit by a NullReferenceException unless you take the time to make sure that your property is initialized with an empty list in the constructor, or dump the terse syntax for `new List {1,2,3}`. Does this also fall under 'no good reason', or is there a reason to not have the syntax initialize the property in this context? – Jonathon Chase Jan 26 '18 at 20:46
  • 1
    @JonathonChase: *Try to design the feature you want and see what happens.* Remember, you need to design the feature to have sensible behaviours even in unusual circumstances, and you need to design it so that if there is a compile-time error, that the user understands the error and can fix it. Start with an easy example, like `List` and then start asking yourself harder questions. What if `x` has a private setter; should it be called? What if `x` is readonly? What if it is a subtype of `List` that doesn't have a default constructor? What if it's a supertype? – Eric Lippert Jan 26 '18 at 20:53
  • "_Try to design the feature you want and see what happens._" The most sensible advice seems so obvious in hindsight. I'll work on folding that into my process. – Jonathon Chase Jan 26 '18 at 21:11