As you know, it is not allowed to use the Array-initialisation syntax with Lists. It will give a compile-time error. Example:
List<int> test = { 1, 2, 3} // At compilation the following error is shown: // Can only use array initializer expressions to assign to array types.
However today I did the following (very simplified):
class Test { public List<int> Field; } List<Test> list = new List<Test> { new Test { Field = { 1, 2, 3 } } };
The code above compiles just fine, but when run it will give a "Object references is not set to an object" run-time error.
I would expect that code to give a compile-time error. My question to you is: Why doesn't it, and are there any good reasons for when such a scenario would run correctly?
This has been tested using .NET 3.5, both .Net and Mono compilers.
Cheers.
I think this is a by-design behavior. The Test = { 1, 2, 3 }
is compiled into code that calls Add
method of the list stored in the Test
field.
The reason why you're getting NullReferenceException
is that Test
is null
. If you initialize the Test
field to a new list, then the code will work:
class Test { public List<int> Field = new List<int>(); } // Calls 'Add' method three times to add items to 'Field' list var t = new Test { Field = { 1, 2, 3 } };
It is quite logical - if you write new List<int> { ... }
then it creates a new instance of list. If you don't add object construction, it will use the existing instance (or null
). As far as I can see, the C# spec doesn't contain any explicit translation rule that would match this scenario, but it gives an example (see Section 7.6.10.3):
A List<Contact>
can be created and initialized as follows:
var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } };
which has the same effect as
var contacts = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); contacts.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); contacts.Add(__c2);
where __c1
and __c2
are temporary variables that are otherwise invisible and inaccessible.
I would expect that code to give a compile-time error.
Since your expectation is contrary to both the specification and the implementation, your expectation is going to go unfulfilled.
Why doesn't it fail at compile time?
Because the specification specifically states that is legal in section 7.6.10.2, which I quote here for your convenience:
A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the field or property, the elements given in the initializer are added to the collection referenced by the field or property.
when would such code run correctly?
As the spec says, the elements given in the initializer are added to the collection referenced by the property. The property does not reference a collection; it is null. Therefore at runtime it gives a null reference exception. Someone has to initialize the list. I would recommend changing the "Test" class so that its constructor initializes the list.
What scenario motivates this feature?
LINQ queries need expressions, not statements. Adding a member to a newly-created collection in a newly-created list requires calling "Add". Since "Add" is void-returning, a call to it can only appear in an expression statement. This feature allows you to either create a new collection (with "new") and populate it, or populate an existing collection (without "new"), where the collection is a member of an object you are creating as the result of a LINQ query.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With