2015-10-21 4 views
1

Почему возникает исключение NullReferenceException при попытке установить значение X в коде ниже? Он отлично работает, когда я использую ключевое слово new при инициализации B, но почему он компилируется отлично без new, а затем сбой во время выполнения?NullReferenceException при инициализации объекта

https://dotnetfiddle.net/YNvPog

public class A 
{ 
    public _B B; 
    public class _B 
    { 
     public int X; 
    } 
} 

public class Program 
{ 
    public static void Main() 
    { 
     var a=new A{ 
       B={ 
        X=1 
       } 
      }; 
    } 
} 
+0

В частности, см. Раздел ** Косвенный ** в принятом ответе. –

+1

Потому что в вашем примере вы не инициализируете B. Вы в основном делаете a.B.X = 1, где B все еще null. –

+0

Вы спрашиваете, почему вы получили NRE или почему компилятор не предупреждает вас об использовании переменной B, не создавая ее раньше? – Steve

ответ

3

Initialization синтаксис может быть сложнее. В вашем коде вы пытаетесь установить значение a.B.X, не устанавливая сначала значение B. Ваш код переводит на:

var a = new A(); 
a.B.X = 1; 

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

Как вы отметили, что это будет работать:

var a=new A{ 
      B= new _B { 
       X=1 
      } 
     }; 

Вы также можете убедиться, что конструктор A «s инициализирует B.

public class A 
{ 
    public _B B = new A._B(); 
    public class _B 
    { 
     public int X; 
    } 
} 

why it compiles fine without new and then fails during runtime?

Это потребовало бы слишком много работы для компилятора, чтобы копаться в коде для A класса и понимают, что B, безусловно, будет нулевой в данный момент: как я указал, вы можете изменить реализация конструктора A, чтобы убедиться, что это не так. Это одна из причин, по которой исключения с нулевой ссылкой являются наиболее распространенным типом исключения.

Лучшей стратегией, чтобы избежать этого, является инициализация всех ваших полей ненулевыми значениями в конструкторе. Если вы не знаете, какое значение им нужно дать, пока ваш конструктор не будет вызван, тогда сделайте свой конструктор такими значениями в качестве параметров. Если вы ожидаете, что одно из ваших полей может не всегда иметь значение, вы можете использовать необязательный тип like my Maybe<> struct, чтобы заставить программистов справиться с этим фактом во время компиляции.

+0

Спасибо, я подумал, что это нечто похожее на 'int [] a = {1,2,3}'. После того, как я разместил вопрос, я добавил ключевое слово «вложенный» и нашел его в спецификациях: «Инициализатор элемента, который указывает инициализатор объекта после знака равенства, является инициализатором вложенного объекта, то есть инициализацией внедренного объекта. Вместо назначения нового значения в поле или свойство назначения в инициализаторе вложенных объектов рассматриваются как назначения членам поля или свойства. Инициализаторы вложенных объектов не могут применяться к свойствам со значением типа или к полям только для чтения со значением типа. " – Franta

+1

@Franta: Я знаю, что вы имеете в виду. Также обратите внимание, что синтаксис инициализатора списка вызывает «Добавить» в коллекции, а не создает новый список. Поэтому обычно разумно всегда инициализировать свойства List с пустыми списками в конструкторе. – StriplingWarrior

+1

Я попытался инициализировать массив ints: https://dotnetfiddle.net/SNlACj Затем он выдает ошибку компиляции: «Невозможно инициализировать объект типа« int [] »с инициализатором коллекции» (4.5) и «'int []' не содержит определения для« Добавить »(Roslyn). Спасибо за объяснение - я думаю, я был бы озадачен этими сообщениями об ошибках, не зная, что происходит. – Franta

Смежные вопросы