C # - «Ссылка на объект не установлена ​​на экземпляр объекта» - PullRequest
2 голосов
/ 21 ноября 2010

В настоящее время я пытаюсь проверить, является ли ссылка нулевой, прежде чем она будет использована MyMethod с:

if (School.ClassRoom.Pupil.Age != null)
        {
            MyMethod(School.ClassRoom.Pupil.Age);
        }

Тем не менее, я все еще получаю «Ссылка на объект не установлена ​​на экземпляр объекта» в первой строке, потому что не только Age null, но также Pupil и ClassRoom также иногда равны null.

У меня возникают те же проблемы с использованием Try, Catch, Наконец, так как я получаю ту же ошибку в части кода Try.

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

Есть ли более простой способ сделать это?

Ответы [ 4 ]

5 голосов
/ 21 ноября 2010

Примечание: ответ ниже был написан в 2010 году, задолго до того, как в C # 6 был введен оператор null условный .


Звучит так, будто вы ищете что-то вроде нулевого безопасного оператора разыменования Groovy, который позволял бы вам писать if (School?.ClassRoom?.Pupil?.Age != null) - но C # не имел такого до C # 6.

Боюсь, что у вас есть для проверки каждого свойства на недействительность, при условии, что оно может быть null:

if (School != null && School.ClassRoom != null && School.ClassRoom.Pupil != null
    && School.ClassRoom.Pupil.Age != null)
{
    MyMethod(School.ClassRoom.Pupil.Age);
}

Конечно, вы можете поместить весь этот блок if, включая сам вызов метода, в вспомогательный метод и просто вызвать его.

Предполагается, что для каждого свойства будет null для начала. Если вы можете спроектировать свои классы так, чтобы нулевые значения даже не допускались - и вы проверяете это в конструкторах и т. Д. - ваш код, вероятно, окажется намного чище.

Стоит отметить, что здесь есть два альтернативных подхода - один, предложенный Крисом в другом ответе, заключается в создании объекта «по умолчанию» для каждого свойства; Я обычно нахожу, что лучше всегда требовать, чтобы в конструкторе было указано "реальное" значение. Объекты по умолчанию без реальных данных могут в конечном итоге привести к ошибкам, которые труднее отследить, чем NullReferenceException проблем, поскольку вы можете долго и долго соглашаться с «фиктивными» данными и просто получить неверный результат в конце. Однако определенно бывают случаи, когда является правильным решением, особенно когда речь идет о коллекциях. Это зависит от ситуации.

РЕДАКТИРОВАТЬ: Саид предложил метод расширения в комментариях. Я предполагаю, что это будет что-то вроде:

public static int? PupilAgeOrNull(this School school)
{
    return school != null && 
           school.ClassRoom != null && 
           school.ClassRoom.Pupil != null
           ? school.ClassRoom.Pupil.Age : null;
}

(Отрегулируйте для типов соответствующим образом.)

Я определенно предпочитаю идею пытаться сохранить вещи ненулевыми в другом месте, но это будет делать, если вам это нужно. Мне кажется, что это неправильно. В основе этого интуитивного ощущения лежит тот факт, что вы начинаете навигацию по трем или четырем свойствам - для меня это выглядит как нарушение Закона Деметры . Теперь я не из тех, кто догматично относится к таким вещам, но использование метода расширения для School кажется мне слишком специфичным для такого длинного пути свойств.

Другая альтернатива, которая также несколько неприятна, IMO, - написать три различных метода расширения:

public static ClassRoom ClassRoomOrNull(this School school)
{
    return school == null ? null : school.ClassRoom;
}

public static Pupil PupilOrNull(this ClassRoom classRoom)
{
    return classRoom == null ? null : classRoom.Pupil;
}

public static int? AgeOrNull(this Pupil pupil)
{
    return pupil == null ? null : pupil.Age;
}

Тогда вы можете написать:

int? age = School.ClassRoomOrNull().PupilOrNull().AgeOrNull();
if (age != null)
{
    MyMethod(age);
}

Это означает, что метод расширения в School не столь специфичен. У вас все еще есть длинная цепочка вызовов методов, и я все же попытался бы изменить дизайн, чтобы избежать этой ситуации, если это возможно, но, по крайней мере, нет такой тесной связи с School до School.ClassRoom.Pupil.Age.

2 голосов
/ 21 ноября 2010

Здесь - это красивое и элегантное решение с деревьями выражений .Попробуйте и наслаждайтесь!

1 голос
/ 21 ноября 2010

«Шаблон нулевого объекта» приходит на помощь.Прочитайте здесь .

Итак, вы можете иметь NullSchool, NullClassRoom, NullPupil, NullAge.

Тогда вам никогда не нужно проверять наличие нулевых вещей, вместо этого вы можете иметь только одинотметьте (или метод, как IsValid () в классе Age, конечно же виртуальный) в MyMethod, чтобы отклонить возраст, если он недопустим.

1 голос
/ 21 ноября 2010

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

if (School != null && School.ClassRoom != null 
  && School.ClassRoom.Pupil != null 
  && School.ClassRoom.Pupil.Age != null) 
{
  ...
}

Однако вы можете написать свой код таким образом, чтобы члены никогда не были null. Таким образом, вы можете избежать проверки на null. Например

class School
{
  private ClassRoom _classRoom = new ClassRoom();

  public ClassRoom ClassRoom 
  {
    get {return _classRoom;}
  }
}

Это даст Школе пустую классную комнату для начала, поэтому она не является null и не может быть установлена ​​на null вне класса, так как свойство не имеет установщика. Вы можете продвинуть эту концепцию вперед, ваш список учеников (я предполагаю, что это будет список) может быть пустым списком, а не пустым экземпляром и т. Д.

...