[英]Null parameter checking in C#
In C#, are there any good reasons (other than a better error message) for adding parameter null checks to every function where null is not a valid value? In C#, are there any good reasons (other than a better error message) for adding parameter null checks to every function where null is not a valid value? Obviously, the code that uses s will throw an exception anyway.
显然,使用 s 的代码无论如何都会抛出异常。 And such checks make code slower and harder to maintain.
而这样的检查会使代码变得更慢、更难维护。
void f(SomeType s)
{
if (s == null)
{
throw new ArgumentNullException("s cannot be null.");
}
// Use s
}
Yes, there are good reasons:是的,有充分的理由:
NullReferenceException
NullReferenceException
可能不明显 Now as for your objections:至于你的反对意见:
And for your assertion:对于你的断言:
Obviously, the code that uses s will throw an exception anyway.
显然,使用 s 的代码无论如何都会抛出异常。
Really?真的吗? Consider:
考虑:
void f(SomeType s)
{
// Use s
Console.WriteLine("I've got a message of {0}", s);
}
That uses s
, but it doesn't throw an exception.这使用
s
,但它不会引发异常。 If it's invalid for s
to be null, and that indicates that something's wrong, an exception is the most appropriate behaviour here.如果
s
为 null 是无效的,并且表明出现问题,则异常是这里最合适的行为。
Now where you put those argument validation checks is a different matter.现在你把这些参数验证检查放在哪里是另一回事。 You may decide to trust all the code within your own class, so not bother on private methods.
您可能决定信任您自己的类中的所有代码,因此不必理会私有方法。 You may decide to trust the rest of your assembly, so not bother on internal methods.
您可能会决定信任程序集的其余部分,所以不要打扰内部方法。 You should almost certainly validate the arguments for public methods.
您几乎肯定应该验证公共方法的参数。
A side note: the single-parameter constructor overload of ArgumentNullException
should just be the parameter name, so your test should be:旁注:
ArgumentNullException
的单参数构造函数重载应该只是参数名称,所以你的测试应该是:
if (s == null)
{
throw new ArgumentNullException("s");
}
Alternatively you can create an extension method, allowing the somewhat terser:或者,您可以创建一个扩展方法,允许更简洁:
s.ThrowIfNull("s");
In my version of the (generic) extension method, I make it return the original value if it's non null, allowing you to write things like:在我的(通用)扩展方法版本中,如果它不为空,我让它返回原始值,允许您编写如下内容:
this.name = name.ThrowIfNull("name");
You can also have an overload which doesn't take the parameter name, if you're not too bothered about that.你也可以有一个不带参数名称的重载,如果你不太在意的话。
I agree with Jon, but I would add one thing to that.我同意 Jon 的观点,但我还要补充一点。
My attitude about when to add explicit null checks is based on these premises:我对何时添加显式空检查的态度基于以下前提:
throw
statements are statements . throw
语句是语句。if
is a statement . if
的结果是一个语句。throw
in if (x == null) throw whatever;
throw
中if (x == null) throw whatever;
If there is no possible way for that statement to be executed then it cannot be tested and should be replaced with Debug.Assert(x != null);
如果要执行该语句则无法进行测试,并应替换为没有可能的方式
Debug.Assert(x != null);
. .
If there is a possible way for that statement to be executed then write the statement, and then write a unit test that exercises it.如果有可能执行该语句的方法,则编写该语句,然后编写一个单元测试来执行它。
It is particularly important that public methods of public types check their arguments in this way;公共类型的公共方法以这种方式检查它们的参数是特别重要的; you have no idea what crazy thing your users are going to do.
你不知道你的用户会做什么疯狂的事情。 Give them the "hey you bonehead, you're doing it wrong!"
给他们一个“嘿,你这个笨蛋,你做错了!” exception as soon as possible.
尽快例外。
Private methods of private types, by contrast, are much more likely to be in the situation where you control the arguments and can have a strong guarantee that the argument is never null;相比之下,私有类型的私有方法更有可能处于您控制参数并且可以强有力地保证参数永远不会为空的情况下; use an assertion to document that invariant.
使用断言来记录该不变量。
I've been using this for a year now:我已经使用这个一年了:
_ = s ?? throw new ArgumentNullException(nameof(s));
It's a oneliner, and the discard ( _
) means there's no unnecessary allocation.这是一个oneliner,丢弃(
_
)意味着没有不必要的分配。
Without an explicit if
check, it can be very difficult to figure out what was null
if you don't own the code.如果没有明确的
if
检查,如果您不拥有代码,就很难弄清楚什么是null
。
If you get a NullReferenceException
from deep inside a library without source code, you're likely to have a lot of trouble figuring out what you did wrong.如果您在没有源代码的情况下从库的深处获得
NullReferenceException
,您可能很难弄清楚自己做错了什么。
These if
checks will not make your code noticeably slower.这些
if
检查不会使您的代码明显变慢。
Note that the parameter to the ArgumentNullException
constructor is a parameter name, not a message.请注意,
ArgumentNullException
构造函数的参数是参数名称,而不是消息。
Your code should be你的代码应该是
if (s == null) throw new ArgumentNullException("s");
I wrote a code snippet to make this easier:我写了一个代码片段来使这更容易:
<?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>
如果您需要一种更好的方法来确保不会将任何空对象作为参数,您可能需要查看代码契约。
It saves some debugging, when you hit that exception.当您遇到该异常时,它可以节省一些调试。
The ArgumentNullException states explicitly that it was "s" that was null. ArgumentNullException 明确指出它是“s”为空。
If you don't have that check and let the code blow up, you get a NullReferenceException from some unidentified line in that method.如果您没有进行该检查并让代码崩溃,则会从该方法中的某个未识别的行中获得 NullReferenceException。 In a release build you don't get line numbers!
在发布版本中,您不会获得行号!
In C# 11 preview it is super cool!!在C# 11预览中它是超级酷!
You just need to add two exclamation marks !!
你只需要加两个感叹号
!!
at the end of the parameter name.在参数名称的末尾。 This will do the null parameter checking.
这将执行 null 参数检查。
void f(SomeType s!!)
{
// Use s
}
This is equivalent to:这相当于:
void f(SomeType s)
{
if (s is null)
{
throw new ArgumentNullException(nameof(s));
}
// Use s
}
A good explanation article about it can be found here .可以在这里找到一篇关于它的很好的解释文章。
The main benefit is that you're being explicit with the requirements of your method right from the start.主要的好处是你从一开始就明确你的方法的要求。 This makes it clear to other developers working on the code that it is truly an error for a caller to send a null value to your method.
这让其他处理代码的开发人员清楚地知道,调用者向您的方法发送空值确实是一个错误。
The check will also halt the execution of the method before any other code executes.该检查还将在任何其他代码执行之前停止该方法的执行。 That means you won't have to worry about modifications being made by the method that are left unfinished.
这意味着您不必担心未完成的方法所做的修改。
Original code:原始代码:
void f(SomeType s)
{
if (s == null)
{
throw new ArgumentNullException("s cannot be null.");
}
// Use s
}
Rewrite it as:将其改写为:
void f(SomeType s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
}
The reason to rewrite using nameof
is that it allows for easier refactoring.使用
nameof
重写的原因是它允许更容易重构。 If the name of your variable s
ever changes, then the debugging messages will be updated as well, whereas if you just hardcode the name of the variable, then it will eventually be outdated when updates are made over time.如果变量
s
名称发生变化,那么调试消息也将更新,而如果您只是硬编码变量的名称,那么随着时间的推移进行更新时,它最终会过时。 It's a good practice used in the industry.这是行业中使用的一个很好的做法。
int i = Age ?? 0;
So for your example:所以对于你的例子:
if (age == null || age == 0)
Or:要么:
if (age.GetValueOrDefault(0) == 0)
Or:要么:
if ((age ?? 0) == 0)
Or ternary:或三元:
int i = age.HasValue ? age.Value : 0;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.