Список <int> test = {1, 2, 3} - это функция или ошибка?
Как вы знаете, не разрешается использовать синтаксис инициализации Array со списками. Это даст ошибку времени компиляции. Пример:
List<int> test = { 1, 2, 3}
// At compilation the following error is shown:
// Can only use array initializer expressions to assign to array types.
Однако сегодня я сделал следующее (очень упрощенное):
class Test
{
public List<int> Field;
}
List<Test> list = new List<Test>
{
new Test { Field = { 1, 2, 3 } }
};
Приведенный выше код компилируется просто отлично, но при запуске он даст сообщение об ошибке "Объект ссылается не на объект".
Я бы ожидал, что код даст ошибку времени компиляции. Мой вопрос к вам: почему это не так, и есть ли веские причины, когда такой сценарий будет работать правильно?
Это было протестировано с использованием .NET 3.5, как компиляторов .Net, так и Mono.
Приветствия.
Ответы
Ответ 1
Я думаю, что это поведенческое поведение. Test = { 1, 2, 3 }
скомпилирован в код, который вызывает метод Add
списка, хранящегося в поле Test
.
Причина, по которой вы получаете NullReferenceException
, состоит в том, что Test
- null
. Если вы инициализируете поле Test
в новом списке, тогда код будет работать:
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 } };
Это вполне логично - если вы пишете new List<int> { ... }
, то он создает новый экземпляр списка. Если вы не добавите конструкцию объекта, он будет использовать существующий экземпляр (или null
). Насколько я вижу, спецификация С# не содержит явного правила перевода, которое бы соответствовало этому сценарию, но оно дает пример (см. Раздел 7.6.10.3):
A List<Contact>
может быть создан и инициализирован следующим образом:
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" }
}
};
который имеет тот же эффект, что и
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);
где __c1
и __c2
являются временными переменными, которые в противном случае являются невидимыми и недоступными.
Ответ 2
Я бы ожидал, что код даст ошибку времени компиляции.
Поскольку ваше ожидание противоречит спецификации и реализации, ваше ожидание будет невыполненным.
Почему он не работает во время компиляции?
Поскольку спецификация конкретно заявляет, что это законно в разделе 7.6.10.2, которое я цитирую здесь для вашего удобства:
Инициализатор членов, который задает инициализатор коллекции после знака равенства, является инициализацией встроенной коллекции. Вместо того, чтобы назначать новую коллекцию для поля или свойства, элементы, указанные в инициализаторе, добавляются в коллекцию, на которую ссылаются поле или свойство.
если бы такой код работал правильно?
Как говорит спецификация, элементы, указанные в инициализаторе, добавляются в коллекцию, на которую ссылается свойство. Свойство не ссылается на коллекцию; он равен нулю. Поэтому во время выполнения он дает исключение с использованием NULL. Кто-то должен инициализировать список. Я бы рекомендовал изменить класс "Тест", чтобы его конструктор инициализировал список.
Какой сценарий мотивирует эту функцию?
Запросы LINQ требуют выражений, а не операторов. Добавление члена в недавно созданную коллекцию во вновь созданном списке требует вызова "Добавить". Поскольку "Добавить" возвращается в void, вызов к нему может появляться только в выражении. Эта функция позволяет вам либо создать новую коллекцию (с "новым" ), либо заполнить ее, либо заполнить существующую коллекцию (без "новой" ), где коллекция является членом объекта, который вы создаете в результате LINQ запрос.
Ответ 3
Этот код:
Test t = new Test { Field = { 1, 2, 3 } };
Переводится на это:
Test t = new Test();
t.Field.Add(1);
t.Field.Add(2);
t.Field.Add(3);
Так как Field
- null
, вы получаете NullReferenceException
.
Это называется инициализатор коллекции, и он будет работать в вашем первом примере, если вы это сделаете:
List<int> test = new List<int> { 1, 2, 3 };
Вам действительно нужно что-то новое, чтобы иметь возможность использовать этот синтаксис, т.е. инициализатор коллекции может появляться только в контексте выражения создания объекта. В спецификации С#, раздел 7.6.10.1, это синтаксис выражения для создания объекта:
object-creation-expression:
new type ( argument-list? ) object-or-collection-initializer?
new type object-or-collection-initializer
object-or-collection-initializer:
object-initializer
collection-initializer
Итак, все начинается с выражения new
. Внутри выражения вы можете использовать инициализатор коллекции без new
(раздел 7.6.10.2):
object-initializer:
{ member-initializer-list? }
{ member-initializer-list , }
member-initializer-list:
member-initializer
member-initializer-list , member-initializer
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer // here it recurses
Теперь, что вам действительно не хватает, это какой-то литерал в списке, что было бы очень удобно. Я предложил один такой литерал для перечислений здесь.
Ответ 4
var test = (new [] { 1, 2, 3}).ToList();
Ответ 5
Причиной этого является то, что вторым примером является инициализатор списка членов, и выражение MemberListBinding из System.Linq.Expressions дает представление об этом - более подробно см. мой ответ на этот другой вопрос: Какие примеры выражений MemberBinding LINQ?
Этот тип инициализатора требует, чтобы список уже был инициализирован, так что добавленная вами последовательность может быть добавлена к нему.
В результате - синтаксически в коде нет ничего плохого - NullReferenceException
- это ошибка времени выполнения, вызванная тем, что List фактически не был создан. Конструктор по умолчанию, который new
список или встроенный new
в теле кода, решит ошибку времени выполнения.
Что касается того, почему существует разница между этой и первой строкой кода - в вашем примере это не разрешено, потому что этот тип выражения не может быть в правой части назначения, потому что фактически ничего не создает, это только сокращение для Add
.
Ответ 6
Измените код следующим образом:
class Test
{
public List<int> Field = new List<int>();
}
Причина в том, что вы должны явно создать объект коллекции, прежде чем вы сможете поместить в него элементы.