Проверка нулевого параметра в С#

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

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

  // Use s
}

Ответ 1

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

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

Теперь о ваших возражениях:

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

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

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

Действительно? Рассмотрим:

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

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

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

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

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

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

s.ThrowIfNull("s");

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

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

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

Ответ 2

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

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

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

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

Если существует возможный способ выполнения этого оператора, напишите инструкцию, а затем напишите unit test, который его использует.

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

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

Ответ 3

Без явной проверки 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

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

Ответ 5

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

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

Ответ 6

Сохраняет некоторую отладку при удалении этого исключения.

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

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

Ответ 7

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

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