Проверка нулевых параметров в C#


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

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

  // Use s
}
9 65

9 ответов:

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

  • он точно определяет, что такое null, что может быть не очевидно из 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");

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

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

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

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

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

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

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

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

без явного 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>

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

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

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

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

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

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

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

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

это oneliner, и отбросить (_) означает, что нет ненужного выделения.

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;

Исходный Код:

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