C#でのnullパラメータチェック


85

C#では、nullが有効な値ではないすべての関数にパラメーターnullチェックを追加する正当な理由(より良いエラーメッセージ以外)はありますか?明らかに、sを使用するコードはとにかく例外をスローします。そして、そのようなチェックはコードを遅くし、維持するのを難しくします。

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

  // Use s
}

12
単純なnullチェックによって、コードが大幅に(または測定可能な程度に)遅くなることを真剣に疑っています。
ハインジ2011

まあ、それは「Uses」が何をするかに依存します。「returns.SomeField」だけの場合、追加のチェックによってメソッドの速度が1桁遅くなる可能性があります。
kaalus 2011

10
@kaalus:たぶん?おそらく私の本ではそれをカットしていません。テストデータはどこにありますか?そして、そもそもそのような変更がアプリケーションのボトルネックになる可能性はどのくらいありますか?
Jon Skeet 2011

@ジョン:その通りです。ここには確かなデータはありません。JITインライン化がこの余分な「if」の影響を受け、分岐が非常に高価なWindows PhoneまたはXbox向けに開発する場合、非常に偏執的になる可能性があります。
kaalus 2011

3
@kaalus:大きな違いがあることを証明した後、非常に特殊なケースでコードスタイルを調整するか、Debug.Assertを使用して(この場合のみ、Ericの回答を参照)、特定のビルドでのみチェックがオンになるようにします。
Jon Skeet 2011

回答:


163

はい、正当な理由があります:

  • nullを正確に識別しますが、これは NullReferenceException
  • 他の条件が値の参照解除を意味しない場合でも、無効な入力でコードが失敗するようにします
  • これにより、最初の間接参照の前に到達する可能性のある他の副作用がメソッドに発生する前に、例外が発生します。
  • これは、パラメータを他の何かに渡した場合、それらの契約に違反しいないことを確信できることを意味します
  • メソッドの要件を文書化します(もちろん、コードコントラクトを使用する方がさらに優れています)

今あなたの異議について:

  • 遅い:これが実際にコードのボトルネックであることがわかりましたか、それとも推測していますか?ヌルチェックは非常に迅速であり、ほとんどの場合、ボトルネックになることはありません
  • それはコードを維持するのを難しくします:私は反対だと思います。パラメータをnullにできるかどうかが明確になり、その条件が適用されると確信できるコードを使用する方が簡単だと思います。

そしてあなたの主張のために:

明らかに、sを使用するコードはとにかく例外をスローします。

本当に?考えてみましょう:

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

を使用しますsが、例外はスローされません。以下のために、それは無効だ場合はsnullにすること、そしてそれはその何かの間違ったことを示し、例外はここで最も適切な行動です。

ここで、これらの引数検証チェックをどこに置くかは別の問題です。自分のクラス内のすべてのコードを信頼することを決定できるので、プライベートメソッドを気にしないでください。アセンブリの残りの部分を信頼することを決定できるので、内部メソッドを気にしないでください。パブリックメソッドの引数をほぼ確実に検証する必要があります。

補足:の単一パラメーターコンストラクターのオーバーロードはArgumentNullExceptionパラメーター名である必要があるため、テストは次のようになります。

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

または、拡張メソッドを作成して、やや簡潔にすることもできます。

s.ThrowIfNull("s");

私のバージョンの(汎用)拡張メソッドでは、null以外の場合は元の値を返すようにして、次のように記述できるようにします。

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

あまり気にしないのであれば、パラメータ名をとらないオーバーロードを設定することもできます。


8
拡張メソッドの場合は+1。私は、次のような他の検証を含め、まったく同じことを行いましThrowIfEmptyICollection
Davy8 2011

5
維持するのが難しいと思う理由:毎回プログラマーの介入を必要とするすべての手動メカニズムと同様に、これは間違いや脱落を招きやすく、コードが乱雑になります。薄片状の安全性は、安全性がまったくないよりも悪いです。
kaalus 2012

8
@kaalus:テストにも同じ態度を適用しますか?「私のテストでは、考えられるすべてのバグを検出できないため、何も記述しません」?安全メカニズム機能すると、問題を見つけやすくなり、他の場所への影響を減らすことができます(問題を早期に発見することで)。それは...それは10のうち、0回起こっよりもまだましだ10のうち9回、発生した場合
ジョンスキート

2
@DoctorOreo:私は使用しませんDebug.Assert。開発よりも本番環境で(実際のデータを台無しにする前に)エラーをキャッチすることがさらに重要です。
Jon Skeet 2013年

2
C#6.0では、使用することもできます throw new ArgumentNullException(nameof(s))
hrzafer 2015

52

私はジョンに同意しますが、それに1つ追加します。

明示的なnullチェックをいつ追加するかについての私の態度は、次の前提に基づいています。

  • ユニットテストでプログラム内のすべてのステートメントを実行する方法が必要です。
  • throwステートメントはステートメントです。
  • 結果はifある声明
  • そのため、行使するための方法があるはずthrowでをif (x == null) throw whatever;

そのステートメントを実行する方法ない場合はテストできないため、に置き換える必要がありDebug.Assert(x != null);ます。

そのステートメントを実行するための可能な方法がある場合は、ステートメントを記述してから、それを実行する単体テストを記述します。

パブリック型のパブリックメソッドがこの方法で引数をチェックすることは特に重要です。あなたはあなたのユーザーがどんなクレイジーなことをしようとしているのか分かりません。彼らに「やあ、骨の折れる、あなたはそれを間違っている!」と言ってください。できるだけ早く例外。

対照的に、プライベート型のプライベートメソッドは、引数を制御する状況にある可能性がはるかに高く、引数が決してnullにならないことを強く保証できます。アサーションを使用して、その不変条件を文書化します。


22

私はこれを1年間使用しています:

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

これはワンライナーであり、破棄(_)は不要な割り当てがないことを意味します。


8

明示的な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>

5

パラメータとしてnullオブジェクトを取得しないようにするためのより良い方法が必要な場合は、コードコントラクトを確認することをお勧めします。


2

主な利点は、最初からメソッドの要件を明示していることです。これにより、コードで作業している他の開発者にとって、呼び出し元がメソッドにnull値を送信することは本当にエラーであることが明らかになります。

このチェックは、他のコードが実行される前にメソッドの実行も停止します。つまり、未完成のままになっているメソッドによって行われる変更について心配する必要はありません。


2

その例外が発生した場合、デバッグをいくらか節約できます。

ArgumentNullExceptionは、nullであるのは「s」であると明示的に示しています。

そのチェックがなく、コードが機能しなくなった場合、そのメソッドの不明な行からNullReferenceExceptionが発生します。リリースビルドでは、行番号を取得できません。


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

[編集]を使用して書き直す理由nameofは、リファクタリングが容易になるためです。変数の名前がs変更されると、デバッグメッセージも更新されますが、変数の名前をハードコーディングするだけの場合は、時間の経過とともに更新が行われると、最終的には古くなります。これは、業界で使用される優れた方法です。


変更を提案する場合は、その理由を説明してください。また、尋ねられている質問に答えていることを確認してください。
codeCaster 2017年

-1
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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.