Кажется, я столкнулся с серьезными проблемами с NullReferenceExceptions.

Недавно я разрабатываю программное обеспечение, которое анализирует и отображает XML-информацию с веб-сайта. Достаточно просто, правда?

Я получаю ЗАГРУЗКИ исключений NullReferenceExceptions. Например, этот метод:

private void SetUserFriends(List<Friend> list)
{
    int x = 40;
    int y = 3;

    if (list != null)
    {
        foreach (Friend friend in list)
        {
            FriendControl control = new FriendControl();
            control.ID = friend.ID;
            control.URL = friend.URL;
            control.SetID(friend.ID);
            control.SetName(friend.Name);
            control.SetImage(friend.Photo);

            control.Location = new Point(x, y);
            panel2.Controls.Add(control);

            y = y + control.Height + 4;
        } 
    }
}

Мне пришлось обернуть уродливое as sin If вокруг реального цикла foreach, чтобы предотвратить исключение.

Я чувствую, что просто накладываю пластырь на спущенное колесо, вместо того, чтобы решать проблему. Как я могу решить эту проблему? Может быть, мне стоит прочитать книгу о шаблонах программирования или что нет?

На самом деле я потерялся. Я, наверное, задаю неправильные вопросы.


person Sergio Tapia    schedule 12.07.2010    source источник
comment
Вы должны смотреть на код, который вызывает SetUserFriends. Если вы предполагаете, что список друзей не должен быть null (я бы сказал, что это достаточно справедливое предположение), то ошибка заключается в том, что передается в null. Используйте отладчик для поиска в стеке вызовов при получении исключения.   -  person Dean Harding    schedule 12.07.2010
comment
Лучше проверьте, почему у вас есть пустая ссылка на список, а не на пустой объект списка.   -  person BenV    schedule 12.07.2010
comment
Это второстепенное замечание, но я бы поспорил за принятие IEnumerable ‹Friend›, чтобы метод не требовал от вызывающей стороны использования определенного класса коллекции.   -  person Steven Sudit    schedule 12.07.2010
comment
@Steven или, по крайней мере, IList, если вам нужны возможности списка   -  person Darko Z    schedule 12.07.2010
comment
@Darko: Да, определенно. Каким бы ни был минимально достаточный интерфейс.   -  person Steven Sudit    schedule 12.07.2010
comment
@Sergio, я бы порекомендовал книгу Роберта К. Мартина «Чистый код». Примеры написаны на Java, и кажется, что вы задавали вопросы по C #.   -  person Jerod Houghtelling    schedule 12.07.2010


Ответы (6)


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

if (list == null)
{
    throw new ArgumentNullException(list);
}

Это распространенный шаблон защитного программирования - убедитесь, что предоставленные вами данные проходят базовую проверку работоспособности.

Теперь, если вы сами явно вызываете этот метод и обнаруживаете, что этот метод получает нулевой параметр list, когда вы этого не ожидаете, пора взглянуть на логику вызывающего метода. Лично я предпочитаю передавать пустой список, когда у меня нет элементов, в отличие от null, чтобы избежать особых случаев, подобных этому.

person Michael Petrotta    schedule 12.07.2010
comment
+1 Если вы будете делать это последовательно, вы с болью узнаете, как писать код, который избегает непреднамеренных обнулений. - person Rex M; 12.07.2010

Я, вероятно, собираюсь получить отрицательное голосование от толпы "без множественного выхода", но я обычно справляюсь с этим с помощью простой проверки в самом начале метода:

if (list == null || list.Count == 0) return;

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

Но я согласен с кодекой в ​​том, что вам нужно посмотреть на вызывающий код и подумать, можно ли его улучшить оттуда.

person Darko Z    schedule 12.07.2010
comment
Этот подход может работать в данном конкретном случае, но в целом он приводит к очень тонким ошибкам. В большинстве случаев мы не можем разумно вывести, что намеревался сделать вызывающий, передав null, поэтому безопаснее всего прервать выполнение с исключением. - person Rex M; 12.07.2010
comment
Следует задокументировать, принимает ли метод значение null, и (в идеале) что он делает, если это предварительное условие не выполняется. Я согласен с @Rex, что тихий возврат часто может повредить отладке. - person Matthew Flaschen; 12.07.2010
comment
Я полностью согласен с вами обоими, но есть случаи, когда это может быть полезно - person Darko Z; 12.07.2010
comment
Если код не должен принимать значение NULL, то молчаливый сбой будет плохим, и правильным ответом будет выброс исключения. Однако вполне может быть, что нулевой допустимый ввод. Это зависит от обстоятельств, так что не будем торопиться. - person Steven Sudit; 12.07.2010
comment
@Steven в этом случае OP жалуется на все проблемы, в которые он попал, пропустив нули. Так... :) - person Rex M; 12.07.2010
comment
@Rex: Точка взята. Если для него будет выбрано значение null, они будут вынуждены взглянуть на стек вызовов и понять, как прошел null. Сказав это, мы не хотим создавать ложное впечатление, что бросание - единственный разумный ответ на null. Чтобы дать глупый пример, представьте, если бы мы применили это догматически к String.IsNullOrEmpty. ;-) - person Steven Sudit; 12.07.2010

Похоже, что защитное программирование и проверка параметров - это то, что вы ищете.

Как говорили другие, для вас подойдет простая проверка параметров:

if (list == null)
    throw new ArgumentNullException("list");

В качестве альтернативы, если вы устали постоянно писать подобные проверки для каждого параметра, вы можете проверить одну из многих библиотек принудительного исполнения предварительных условий .NET с открытым исходным кодом. Мне нравятся CuttingEdge.Conditions.

Таким образом, вы можете использовать что-то вроде этого:

Condition.Requires(list, "list").IsNotNull();

Однако настройка предварительного условия, подобного любому из вышеперечисленных, просто укажет, что ваш метод не принимает значения NULL. Ваша проблема по-прежнему будет заключаться в том, что вы передаете в метод значения NULL! Чтобы исправить это, вам нужно будет проверить, что вызывает ваши методы, и выяснить, почему передаются нулевые объекты.

person Matthew King    schedule 12.07.2010
comment
И +1 за ссылку на CuttingEdge.Conditions ;-) - person Steven; 19.09.2010

Помимо выдачи исключений ArgumentNullException, существует также нечто, называемое «Null Obejct Pattern», которое вы можете использовать, если хотите, чтобы передавал нулевое значение, например, чтобы указать, что чего-то не существует, но не хочу явно проверять нули. По сути, это класс-заглушка, который реализует тот же интерфейс, но его методы обычно либо пусты, либо возвращают ровно столько, чтобы они завершились. http://en.wikipedia.org/wiki/Null_Object_pattern

Также полезно для типов значений, которые не могут легко выразить свое несуществование, не имея возможности иметь значение NULL.

person Spike    schedule 12.07.2010
comment
Интересно, но я не уверен, применимо ли это здесь, поскольку ближе всего к шаблону нулевого объекта здесь будет пустой список. Другая проблема, на которую я указал в другом месте, заключается в том, что этот метод, вероятно, должен принимать IEnumerable вместо List. - person Steven Sudit; 12.07.2010
comment
Верно, но он упомянул, что это всего лишь один из примеров его ЗАГРУЗКИ исключений NullReferenceExceptions. Это могло быть правильным решением для некоторых из его других. Для этого я бы тоже выбрал исключение. - person Spike; 12.07.2010
comment
У меня сложилось впечатление, что дело не столько в том, что я забыл проверить на ноль, а в том, что я вообще не знаю, почему он был нулевым. Цель шаблона нулевого объекта - избежать нулевых проверок, но он не дает никакого понимания более глубокой проблемы здесь. Однако исключения будут, потому что они ограничат распространение нулей, что упростит отслеживание трассировки стека до того места, где проникает ноль. - person Steven Sudit; 12.07.2010

Я бы вернулся раньше (или выбросил бы InvalidArgumentException раньше), если бы получил недопустимый ввод.

Например:

private void SetUserFriends(List<Friend> list) 
{ 
    if (list == null) 
        return;

    /* Do stuff */
}

В качестве альтернативы вы можете использовать общий шаблон объединения NULL:

private void SetUserFriends(List<Friend> list) 
{ 
    list = list ?? new List<Friend>();

    /* Do Stuff */
}
person Matt Mitchell    schedule 12.07.2010
comment
Однако предложение о слиянии с нулевым значением немного нелепо для этого сценария. - person Dan Tao; 12.07.2010
comment
Ранний возврат и бросок - это чрезвычайно разные подходы. Что вы рекомендуете? - person Rex M; 12.07.2010
comment
Хм, если вы собираетесь сгенерировать исключение, правильным будет ArgumentNullException, как показал пример Майкла. - person Steven Sudit; 12.07.2010
comment
@ Дэн: Согласен. Нет смысла размещать пустой список только для того, чтобы с ним ничего не делать. - person Steven Sudit; 12.07.2010
comment
@Dan Согласился, но OP действительно спросил, как вы в целом справляетесь с этим, и я просто хотел дать им представление. @Rex, в некотором смысле они не такие уж и разные (они мешают методу делать свое дело), ​​но он очень контекстный. Если вы подошли к прилавку и забыли что-нибудь принести для покупки, мне не нужно выгонять вас из своего магазина (я просто не могу обработать). Если вы дадите мне чек, который отскакивает, возможно, пришло время обратить на это чье-то внимание. - person Matt Mitchell; 12.07.2010

Вы действительно задаете неправильный вопрос. Правильный вопрос: «представляет ли null недопустимый ввод или флаг, который означает X».

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

person jmoreno    schedule 13.11.2016