在C#中,是否有任何充分的理由(除了更好的错误消息之外)向每个无效值都无效的函数添加参数无效检查?显然,使用s的代码仍然会引发异常。这样的检查会使代码变慢并且难以维护。
void f(SomeType s)
{
if (s == null)
{
throw new ArgumentNullException("s cannot be null.");
}
// Use s
}
在C#中,是否有任何充分的理由(除了更好的错误消息之外)向每个无效值都无效的函数添加参数无效检查?显然,使用s的代码仍然会引发异常。这样的检查会使代码变慢并且难以维护。
void f(SomeType s)
{
if (s == null)
{
throw new ArgumentNullException("s cannot be null.");
}
// Use s
}
Answers:
是的,有充分的理由:
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");
在我的(通用)扩展方法版本中,如果它不为null,则使它返回原始值,从而使您可以编写以下内容:
this.name = name.ThrowIfNull("name");
如果您不太担心该过载,那么也可以使用不带参数名称的重载。
ThrowIfEmpty
对ICollection
Debug.Assert
。与在开发中相比,发现生产中的错误(在错误破坏真实数据之前)更为重要。
throw new ArgumentNullException(nameof(s))
我同意乔恩的观点,但是我要补充一件事。
我对何时添加显式null检查的态度基于以下前提:
throw
陈述是陈述。if
是一个陈述。throw
在if (x == null) throw whatever;
如果无法执行该语句,则无法对其进行测试,应将其替换为Debug.Assert(x != null);
。
如果有可能执行该语句,请编写该语句,然后编写执行该语句的单元测试。
这是特别是公共类型的公共方法,检查他们的论点以这种方式很重要; 您不知道用户将要做什么疯狂的事情。给他们“嘿,你这骨头,你做错了!” 尽快例外。
相比之下,私有类型的私有方法更可能出现在您控制参数的情况下,并且可以有力保证参数永远不会为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>
主要好处是,您从一开始就明确了方法的要求。这使从事代码工作的其他开发人员可以清楚地知道,调用者向您的方法发送null值确实是错误的。
该检查还将在执行任何其他代码之前暂停该方法的执行。这意味着您不必担心该方法未完成的修改。
原始代码:
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
更改,则调试消息也将更新,而如果您仅对变量名进行硬编码,则随着时间的推移,变量名最终将过时。这是工业上的好习惯。
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;