简体   繁体   English

Null 参数检查 C#

[英]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:是的,有充分的理由:

  • It identifies exactly what is null, which may not be obvious from a NullReferenceException它准确地标识什么是 null,从NullReferenceException可能不明显
  • It makes the code fail on invalid input even if some other condition means that the value isn't dereferenced即使其他一些条件意味着该值没有被取消引用,它也会使代码在无效输入上失败
  • It makes the exception occur before the method could have any other side-effects you might reach before the first dereference它使异常发生该方法可能具有您在第一次取消引用之前可能达到的任何其他副作用之前
  • It means you can be confident that if you pass the parameter into something else, you're not violating their contract这意味着你可以确信,如果你将参数传递给其他东西,你就不会违反他们的合同
  • It documents your method's requirements (using Code Contracts is even better for that of course)它记录了您的方法的要求(当然,使用代码契约更好)

Now as for your objections:至于你的反对意见:

  • It's slower : Have you found this to actually be the bottleneck in your code, or are you guessing?它更慢:您是否发现这实际上是您代码中的瓶颈,还是您在猜测? Nullity checks are very quick, and in the vast majority of cases they're not going to be the bottleneck空值检查非常快,并且在绝大多数情况下它们不会成为瓶颈
  • It makes the code harder to maintain : I think the opposite.它使代码更难维护:我认为相反。 I think it's easier to use code where it's made crystal clear whether or not a parameter can be null, and where you're confident that that condition is enforced.我认为使用代码更容易,因为它清楚地表明参数是否可以为空,并且您确信该条件是强制执行的。

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:我对何时添加显式空检查的态度基于以下前提:

  • There should be a way for your unit tests to exercise every statement in a program.应该有一种方法让您的单元测试来练习程序中的每个语句。
  • throw statements are statements . throw语句是语句
  • The consequence of an if is a statement . if的结果是一个语句
  • Therefore, there should be a way to exercise the throw in if (x == null) throw whatever;因此,应该有锻炼的方式throwif (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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM