Проверка нулевого параметра в C # - PullRequest
72 голосов
/ 28 сентября 2011

В C # есть ли какие-либо веские причины (кроме лучшего сообщения об ошибке) для добавления проверок нулевого параметра в каждую функцию, где нулевое недопустимое значение? Очевидно, что код, который использует s, все равно выдаст исключение. И такие проверки делают код медленнее и труднее поддерживать.

void f(SomeType s)
{
  if (s == null)
  {
    throw new ArgumentNullException("s cannot be null.");
  }

  // Use s
}

Ответы [ 9 ]

144 голосов
/ 28 сентября 2011

Да, есть веские причины:

  • Он точно определяет, что является нулем, что может быть неочевидно из NullReferenceException
  • Это приводит к сбою кода при неверном вводедаже если какое-то другое условие означает, что значение не разыменовано
  • Это исключение возникает за до , у метода могут быть любые другие побочные эффекты, которые могут возникнуть до первой разыменования
  • Это означает, что вы можете быть уверены, что если вы передадите параметр во что-то другое, вы не нарушите их контракт
  • Он документирует требования вашего метода (используя Code Contracts еще лучше для этого, конечно)

Теперь что касается ваших возражений:

  • Это медленнее : Вы нашли это для на самом деле быть узким местом в вашем коде, или вы догадываетесь?Нулевые проверки очень быстрые, и в подавляющем большинстве случаев они , а не станут узким местом
  • Это затрудняет поддержку кода : я думаюпротивоположный.Я думаю, что проще использовать код, когда он становится кристально ясным, может ли параметр быть нулевым или нет, и вы уверены, что это условие выполняется.

И дляВаше утверждение:

Очевидно, что код, который использует s, все равно выдаст исключение.

Правда?Рассмотрим:

void f(SomeType s)
{
  // Use s
  Console.WriteLine("I've got a message of {0}", s);
}

Использует s, но не выдает исключение.Если для s недопустимо быть нулевым, и это означает, что что-то не так, исключение является наиболее подходящим поведением здесь.

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

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

if (s == null)
{
  throw new ArgumentNullException("s");
}

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

s.ThrowIfNull("s");

В моей версии (универсального) метода расширения я заставляю его возвращать исходное значение, если оно не равно нулю, что позволяет вамнаписать что-то вроде:

this.name = name.ThrowIfNull("name");

Вы также можете иметь перегрузку, которая не принимает имя параметра, если вы не слишком обеспокоены этим.

46 голосов
/ 28 сентября 2011

Я согласен с Джоном, но я хотел бы добавить одну вещь к этому.

Мое отношение к тому, когда добавлять явные проверки на ноль, основано на этих предпосылках:

  • Должен быть способ для ваших модульных тестов выполнять каждое утверждение в программе.
  • throw операторы операторы .
  • Следствием if является оператор .
  • Следовательно, должен быть способ применить throw в if (x == null) throw whatever;

Если существует невозможный способ для выполнения этого оператора, то он не может быть проверен и должен быть заменен на Debug.Assert(x != null);.

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

особенно важно, чтобы публичные методы открытых типов проверяли свои аргументы таким образом;Вы не представляете, что сумасшедшие будут делать ваши пользователи.Дайте им "эй, ты, тупица, ты делаешь это неправильно!"исключение как можно скорее.

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

7 голосов
/ 28 сентября 2011

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

Если вы получите NullReferenceException из глубины библиотеки без исходного кода у вас, вероятно, возникнут большие проблемы с выяснением того, что вы сделали неправильно.

Эти проверки if не сделают ваш код заметно медленнее.


Обратите внимание, что параметр в конструкторе ArgumentNullException - это имя параметра, а не сообщение.
Ваш код должен быть

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

Я написалфрагмент кода, чтобы сделать это проще:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>Check for null arguments</Title>
            <Shortcut>tna</Shortcut>
            <Description>Code snippet for throw new ArgumentNullException</Description>
            <Author>SLaks</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
                <SnippetType>SurroundsWith</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>Parameter</ID>
                    <ToolTip>Paremeter to check for null</ToolTip>
                    <Default>value</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp"><![CDATA[if ($Parameter$ == null) throw new ArgumentNullException("$Parameter$");
        $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>
4 голосов
/ 10 февраля 2018

Я использую это уже год:

_ = s ?? throw new ArgumentNullException(nameof(s));

Это oneliner, и сброс (_) означает, что нет ненужных выделений.

4 голосов
/ 28 сентября 2011

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

2 голосов
/ 28 сентября 2011

Сохраняется некоторая отладка, когда вы нажимаете это исключение.

ArgumentNullException явно заявляет, что это было "s", которое было нулевым.

Если у вас нет этой проверки и позвольтепри выполнении кода вы получаете исключение NullReferenceException из неопознанной строки в этом методе.В сборке релиза вы не получите номера строк!

2 голосов
/ 28 сентября 2011

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

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

0 голосов
/ 12 апреля 2018
int i = Age ?? 0;

Итак, для вашего примера:

if (age == null || age == 0)

Или:

if (age.GetValueOrDefault(0) == 0)

Или:

if ((age ?? 0) == 0)

Или троичные:

int i = age.HasValue ? age.Value : 0;
0 голосов
/ 06 ноября 2017

Оригинальный код:

void f(SomeType s)
{
  if (s == null)
  {
    throw new ArgumentNullException("s cannot be null.");
  }

  // Use s
}

Перепишите его как:

void f(SomeType s)
{
  if (s == null) throw new ArgumentNullException(nameof(s));
}

[Редактировать] Причина перезаписи с использованием nameof заключается в том, что она упрощает рефакторинг. Если имя вашей переменной s когда-либо изменится, то сообщения отладки также будут обновлены, тогда как если вы просто жестко закодируете имя переменной, то со временем оно будет устаревшим, когда обновления будут сделаны с течением времени. Это хорошая практика, используемая в промышленности.

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