web-dev-qa-db-fra.com

Vérification des paramètres nuls en C #

En C #, existe-t-il de bonnes raisons (autres qu'un meilleur message d'erreur) pour ajouter des vérifications nulles de paramètres à chaque fonction où null n'est pas une valeur valide? De toute évidence, le code qui utilise s lèvera de toute façon une exception. Et ces vérifications rendent le code plus lent et plus difficile à maintenir.

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

  // Use s
}
73
kaalus

Oui, il y a de bonnes raisons:

  • Il identifie exactement ce qui est nul, ce qui peut ne pas être évident à partir d'un NullReferenceException
  • Cela fait échouer le code sur une entrée non valide même si une autre condition signifie que la valeur n'est pas déréférencée
  • Il fait se produire l'exception avant la méthode pourrait avoir d'autres effets secondaires que vous pourriez atteindre avant la première déréférence
  • Cela signifie que vous pouvez être sûr que si vous passez le paramètre dans quelque chose d'autre, vous ne violez pas leur contrat
  • Il documente les exigences de votre méthode (utiliser Contrats de code est encore mieux pour cela bien sûr)

Quant à vos objections:

  • Il est plus lent : l'avez-vous trouvé réellement le goulot d'étranglement de votre code, ou devinez-vous? Les contrôles de nullité sont très rapides, et dans la grande majorité des cas, ils ne seront pas le goulot d'étranglement
  • Cela rend le code plus difficile à maintenir : Je pense le contraire. Je pense qu'il est plus facile d'utiliser du code où il est clair que le paramètre peut être nul ou non et où vous êtes sûr que cette condition est appliquée .

Et pour votre affirmation:

De toute évidence, le code qui utilise s lèvera quand même une exception.

Vraiment? Considérer:

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

Cela utilise s, mais il ne lève pas d'exception. S'il n'est pas valide pour que s soit nul et que cela indique que quelque chose ne va pas, une exception est le comportement le plus approprié ici.

Maintenant vous mettez ces vérifications de validation d'argument est une autre affaire. Vous pouvez décider de faire confiance à tout le code de votre propre classe, alors ne vous souciez pas des méthodes privées. Vous pouvez décider de faire confiance au reste de votre Assemblée, alors ne vous souciez pas des méthodes internes. Vous devriez presque certainement valider les arguments pour les méthodes publiques.

Remarque: la surcharge du constructeur à paramètre unique de ArgumentNullException ne doit être que le nom du paramètre, donc votre test doit être:

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

Alternativement, vous pouvez créer une méthode d'extension, permettant le quelque peu terser:

s.ThrowIfNull("s");

Dans ma version de la méthode d'extension (générique), je lui fais retourner la valeur d'origine si elle n'est pas nulle, ce qui vous permet d'écrire des choses comme:

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

Vous pouvez également avoir une surcharge qui ne prend pas le nom du paramètre, si cela ne vous dérange pas trop.

144
Jon Skeet

Je suis d'accord avec Jon, mais j'ajouterais une chose à cela.

Mon attitude quant au moment d'ajouter des vérifications nulles explicites est basée sur ces prémisses:

  • Il devrait y avoir un moyen pour vos tests unitaires d'exercer chaque instruction d'un programme.
  • throw les instructions sont instructions.
  • La conséquence d'un if est un instruction.
  • Par conséquent, il devrait y avoir un moyen d'exercer le throw dans if (x == null) throw whatever;

S'il y a aucun moyen possible pour que cette instruction soit exécutée, elle ne peut pas être testée et doit être remplacée par Debug.Assert(x != null);.

S'il existe un moyen possible d'exécuter cette instruction, écrivez-la, puis rédigez un test unitaire qui l'exerce.

Il est particulièrement important que les méthodes publiques de types publics vérifient leurs arguments de cette manière; vous n'avez aucune idée de ce que vos utilisateurs vont faire de fou. Donnez-leur le "hey you bonehead, vous vous trompez!" exception dès que possible.

Les méthodes privées de types privés, en revanche, sont beaucoup plus susceptibles d'être dans la situation où vous contrôlez les arguments et peuvent avoir une forte garantie que l'argument n'est jamais nul; utilisez une assertion pour documenter cet invariant.

46
Eric Lippert

Sans vérification explicite de if, il peut être très difficile de comprendre ce que était null si vous ne le faites pas posséder le code.

Si vous obtenez un NullReferenceException au fond d'une bibliothèque sans code source, vous aurez probablement beaucoup de mal à comprendre ce que vous avez fait de mal.

Ces vérifications if ne ralentiront pas sensiblement votre code.


Notez que le paramètre du constructeur ArgumentNullException est un nom de paramètre, pas un message.
Votre code doit être

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

J'ai écrit un extrait de code pour faciliter cela:

<?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>
7
SLaks

Vous voudrez peut-être jeter un coup d'œil à Contrats de code si vous avez besoin d'une meilleure façon de vous assurer que vous n'obtenez aucun objet nul en tant que paramètre.

4
AD.Net

Je l'utilise depuis un an maintenant:

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

C'est un oneliner, et le rejet (_) signifie qu'il n'y a pas d'allocation inutile.

4
gldraphael

Il enregistre certains débogage, lorsque vous frappez cette exception.

ArgumentNullException indique explicitement que c'est "s" qui était nul.

Si vous n'avez pas cette vérification et laissez le code exploser, vous obtenez une NullReferenceException à partir d'une ligne non identifiée dans cette méthode. Dans une version, vous n'obtenez pas de numéros de ligne!

2
Hans Kesting

Le principal avantage est que vous êtes explicite avec les exigences de votre méthode dès le départ. Cela montre clairement aux autres développeurs travaillant sur le code que c'est vraiment une erreur pour un appelant d'envoyer une valeur nulle à votre méthode.

La vérification interrompra également l'exécution de la méthode avant l'exécution de tout autre code. Cela signifie que vous n'aurez pas à vous soucier des modifications apportées par la méthode qui restent inachevées.

2
Justin Niessner
int i = Age ?? 0;

Donc pour votre exemple:

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

Ou:

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

Ou:

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

Ou ternaire:

int i = age.HasValue ? age.Value : 0;
0