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
}
Oui, il y a de bonnes raisons:
NullReferenceException
Quant à vos objections:
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 où 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.
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:
throw
les instructions sont instructions.if
est un instruction.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.
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>
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.
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.
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!
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.
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;