Список <int>test = {1, 2, 3} - это особенность или ошибка? - PullRequest
49 голосов
/ 04 февраля 2011

Как вы знаете, нельзя использовать синтаксис инициализации массива со списками. Это даст ошибку во время компиляции. Пример:

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.

Приветствие.

Ответы [ 6 ]

47 голосов
/ 04 февраля 2011

Я думаю, что это не совсем правильное поведение. 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). Насколько я вижу, спецификация C # не содержит никаких явных правил перевода, которые бы соответствовали этому сценарию, но она дает пример (см. Раздел 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 - временные переменные, которые в противном случае невидимы и недоступны.

25 голосов
/ 04 февраля 2011

Я ожидаю, что этот код выдаст ошибку во время компиляции.

Поскольку ваши ожидания противоречат как спецификации, так и реализации, ваши ожидания останутся невыполненными.

Почему оно не терпит неудачу во время компиляции?

Поскольку в спецификации конкретно указано, что это допустимо в разделе 7.6.10.2, который я привожу здесь для вашего удобства:


Инициализатор элемента, который указывает инициализатор коллекциипосле знака равенства происходит инициализация встроенной коллекции.Вместо назначения новой коллекции полю или свойству, элементы, заданные в инициализаторе, добавляются в коллекцию, на которую ссылается поле или свойство.


, когда будет выполняться такой кодправильно?

Как сказано в спецификации, элементы, указанные в инициализаторе, добавляются в коллекцию, на которую ссылается свойство.Свойство не ссылается на коллекцию;это ноль.Поэтому во время выполнения это дает исключение нулевой ссылки.Кто-то должен инициализировать список.Я бы рекомендовал изменить класс «Test», чтобы его конструктор инициализировал список.

Какой сценарий мотивирует эту функцию?

Для запросов LINQ нужны выражения, а не операторы.Добавление члена во вновь созданную коллекцию во вновь созданном списке требует вызова «Добавить».Поскольку «Add» возвращает void, вызов к нему может появиться только в выражении выражения.Эта функция позволяет вам либо создать новую коллекцию (с «новой») и заполнить ее, либо заполнить существующую коллекцию (без «новой»), где коллекция является членом объекта, который вы создаете в результате LINQзапрос.

18 голосов
/ 04 февраля 2011

Этот код:

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 };

Вам действительно нужно что-то новое, чтобы иметь возможность использоватьэтот синтаксис, то есть инициализатор коллекции, может появляться только в контексте выражения создания объекта.В спецификации C #, раздел 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

Теперь, что вы действительно упускаете, так это какой-то литерал списка, который был бы действительноудобно.Я предложил один такой литерал для перечислимых здесь .

3 голосов
/ 04 февраля 2011
var test = (new [] { 1, 2, 3}).ToList();
2 голосов
/ 04 февраля 2011

Причина этого в том, что второй пример является инициализатором списка членов - и выражение MemberListBinding из System.Linq.Expressions дает представление об этом - пожалуйста, смотрите мой ответ на этот другой вопрос для более подробной информации: Чтонекоторые примеры выражений MemberBinding LINQ?

Этот тип инициализатора требует, чтобы список уже был инициализирован, чтобы в него можно было добавить предоставленную вами последовательность.

В результате- синтаксически в коде нет ничего плохого - NullReferenceException - это ошибка времени выполнения, вызванная тем, что список фактически не был создан.Конструктор по умолчанию, который new s в списке или встроенный new в теле кода, решит ошибку времени выполнения.

Что касается разницы между этим и первой строкой кода -в вашем примере это не разрешено, потому что этот тип выражения не может быть справа от назначения, потому что на самом деле ничего не создает , это всего лишь сокращение для Add.

1 голос
/ 04 февраля 2011

Измените свой код на это:

class Test
{
   public List<int> Field = new List<int>();
}

Причина в том, что вы должны явно создать объект коллекции, прежде чем сможете помещать в него элементы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...