C# 中的空参数检查

发布于 2024-12-06 20:41:32 字数 250 浏览 0 评论 0原文

在 C# 中,是否有任何好的理由(除了更好的错误消息之外)向每个 null 不是有效值的函数添加参数 null 检查?显然,使用 s 的代码无论如何都会抛出异常。这样的检查会使代码变慢并且更难维护。

void f(SomeType s)
{
  if (s == null)
  {
    throw new ArgumentNullException("s cannot be null.");
  }

  // Use s
}

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. 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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

情魔剑神 2024-12-13 20:41:32

是的,有充分的理由:

  • 它准确地识别了 null 的内容,这在 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");

在我的版本中(通用)扩展方法,如果它不为空,我让它返回原始值,允许您编写如下内容:

this.name = name.ThrowIfNull("name");

您还可以有一个不采用参数名称的重载,如果您对此不太在意的话。

更新 .NET 6

有一个 新方法.NET API 中的 简化了 null 检查语法。

 ArgumentNullException.ThrowIfNull(someParameter);

Yes, there are good reasons:

  • It identifies exactly what is null, which may not be obvious from a 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.

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. If it's invalid for s to be null, and that indicates that something's wrong, an exception is the most appropriate behaviour here.

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:

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.

Update .NET 6

There is a new method in .NET API which simplifies null check syntax.

 ArgumentNullException.ThrowIfNull(someParameter);
四叶草在未来唯美盛开 2024-12-13 20:41:32

我同意乔恩的观点,但我想补充一件事。

我对何时添加显式空检查的态度基于以下前提:

  • 应该有一种方法让单元测试来执行程序中的每个语句。
  • throw 语句是语句
  • if 的结果是一个语句
  • 因此,应该有一种方法来执行 if (x == null) throwwhat; 中的 throw

如果没有可能的方法如果要执行该语句,则无法对其进行测试,应将其替换为 Debug.Assert(x != null);。

如果有可能执行该语句,则编写该语句,然后编写一个执行该语句的单元测试。

公共类型的公共方法以这种方式检查其参数是特别重要的;你不知道你的用户会做什么疯狂的事情。给他们“嘿,你这个笨蛋,你做错了!”尽快排除。

相比之下,私有类型的私有方法更有可能出现在您控制参数的情况下,并且可以强有力地保证参数永远不为空;使用断言来记录该不变量。

I agree with Jon, but I would add one thing to that.

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.
  • The consequence of an if is a statement.
  • Therefore, there should be a way to exercise the throw in 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);.

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.

〗斷ホ乔殘χμё〖 2024-12-13 20:41:32

我已经使用它一年了:

_ = s ?? throw new ArgumentNullException(nameof(s));

它是一个单行代码,丢弃(_)意味着没有不必要的分配。

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.

冰魂雪魄 2024-12-13 20:41:32

如果没有显式的 if 检查,如果您不拥有该代码,则很难弄清楚 null 是什么。

如果您在没有源代码的情况下从库深处收到 NullReferenceException,您可能很难弄清楚自己做错了什么。

这些 if 检查不会使您的代码明显变慢。


请注意 ArgumentNullException< 的参数/code> 构造函数是参数名称,而不是消息。
你的代码应该是

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>

Without an explicit if check, it can be very difficult to figure out what was null if you don't own the code.

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.

These if checks will not make your code noticeably slower.


Note that the parameter to the ArgumentNullException constructor is a parameter name, not a message.
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
quot;);
        $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>
眼眸 2024-12-13 20:41:32

如果您需要更好的方法,您可能需要查看代码契约确保您没有获取任何空对象作为参数。

You might want to take a look at Code Contracts if you need a nicer way to make sure you do not get any null objects as a parameter.

我乃一代侩神 2024-12-13 20:41:32

C# 11 预览版中,它非常酷!!

您只需在参数名称末尾添加两个感叹号!!即可。这将进行空参数检查。

void f(SomeType s!!)
{
    // Use s
}

这相当于:

void f(SomeType s)
{
    if (s is null)
    {
        throw new ArgumentNullException(nameof(s));
    }

    // Use s
}

可以找到一篇关于它的很好的解释文章

In C# 11 preview it is super cool!!

You just need to add two exclamation marks !! at the end of the parameter name. This will do the null parameter checking.

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.

梦断已成空 2024-12-13 20:41:32

当您遇到异常时,它可以节省一些调试。

ArgumentNullException 明确指出“s”为空。

如果您没有进行该检查并让代码崩溃,那么您会从该方法中的某些未识别的行中得到 NullReferenceException。在发布版本中,您不会获得行号!

It saves some debugging, when you hit that exception.

The ArgumentNullException states explicitly that it was "s" that was null.

If you don't have that check and let the code blow up, you get a NullReferenceException from some unidentified line in that method. In a release build you don't get line numbers!

放我走吧 2024-12-13 20:41:32

主要好处是您从一开始就明确了方法的要求。这使得处理该代码的其他开发人员清楚地知道,调用者向您的方法发送空值确实是一个错误。

该检查还将在执行任何其他代码之前停止该方法的执行。这意味着您不必担心方法所做的修改未完成。

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.

ら栖息 2024-12-13 20:41:32

原始代码:

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 发生变化,那么调试消息也会更新,而如果您只是硬编码变量的名称,那么随着时间的推移更新,它最终会过时。这是业界采用的良好做法。

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. 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. It's a good practice used in the industry.

残疾 2024-12-13 20:41:32
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;
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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文