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("s cannot be null.");
}
// Use s
}
是的,有充分的理由:
NullReferenceException
中明显现在针对你的异议:
至于你的断言:
显然,使用 s 的代码会抛出异常。
真的吗?请考虑以下内容:
void f(SomeType s)
{
// Use s
Console.WriteLine("I've got a message of {0}", s);
}
代码使用了s
,但它并不会抛出异常。如果s
无效且表示有问题时,抛出异常是最合适的行为。
现在问题是在哪里加入这些参数验证检查。这是一个不同的问题。 你可能决定信任类中的所有代码,因此不需要在私有方法上费心。 你可能决定信任程序集的其余部分,所以不需要在内部方法上费心。 但你几乎肯定要验证公共方法的参数。
另外注意:ArgumentNullException的单参数构造函数重载应该只是参数名称,所以你的测试应该是:
if (s == null)
{
throw new ArgumentNullException("s");
}
或者您可以创建一个扩展方法,从而允许使用较为简洁的方式:
s.ThrowIfNull("s");
在我的(通用)扩展方法中,如果原始值非空,我使其返回原始值,让您可以编写以下代码:
this.name = name.ThrowIfNull("name");
如果您不太在意参数名称,还可以创建一个不带参数名称的重载方法。
.NET API中有一个新方法,可以简化null
检查语法。 此处提供了更多信息。
ArgumentNullException.ThrowIfNull(someParameter);
ICollection
进行ThrowIfEmpty
等其他验证。 - Davy8Debug.Assert
。在生产环境中捕获错误(在它们破坏实际数据之前)比在开发过程中更重要。 - Jon Skeetthrow new ArgumentNullException(nameof(s))
。 - hrzafer我同意Jon的观点,但我想补充一点。
我的态度关于何时添加显式的 null 检查基于以下前提:
throw
语句是 语句。if
的后果是一个 语句。if (x == null) throw whatever;
中的 throw
语句。如果没有 任何可能的方式 使该语句被执行,则无法进行测试,应该用 Debug.Assert(x != null);
来替换它。
如果有可能执行该语句,则编写该语句,然后编写一个单元测试来运行它。
对于公共类型的公共方法以这种方式检查其参数非常重要;您不知道用户将要做什么疯狂的事情。尽早给他们“嘿,你这个蠢蛋,你错了!” 异常。
相比之下,私有类型的私有方法更有可能处于控制参数并且可以有强有力保证参数永远不会为 null 的情况下;使用断言来记录不变量。
我现在已经使用这个东西一年了:
_ = s ?? throw new ArgumentNullException(nameof(s));
这是一行代码,并且舍弃 (_
) 表示没有不必要的内存分配。
double
不能为 null,所以在那里当然没有意义。但它可以与 double?
一起使用。 - galdin如果没有明确的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" 为空。
如果你没有进行检查并让代码崩溃,你会从该方法的某个未知行处得到一个 NullReferenceException。在发布版本中,你无法得到行号!
原始代码:
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;