C#:在这种情况下,我是否应该费心检查null?

时间:2009-03-20 09:23:16

标签: c#

假设我有这种扩展方法:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    if(subjects == null)
        throw new ArgumentNullException("subjects");

    return subjects.Count() == 5;
}

你认为这个空检查和异常抛出真的有必要吗?我的意思是,当我使用Count方法时,无论如何都会抛出ArgumentNullException,对吗?

我可以想到我应该这样做的一个原因,但是我想听听别人对此的看法。是的,我问的理由是部分懒惰(想要尽可能少地写),但也因为我认为一堆空检查和异常抛出了一些混乱的方法,这些方法往往最终是他们真的需要。有人应该知道比将null发送到方法更好:p

无论如何,你们有什么想法?


注意: Count()是一种扩展方法,抛出ArgumentNullException,而不是NullReferenceException。见Enumerable.Count<TSource> Method (IEnumerable<TSource>)。如果你不相信我,请自己尝试一下=)


注2:在这里给出的答案之后,我被说服开始检查更多的空值。我仍然很懒,所以我开始使用Lokad Shared Libraries中的Enforce课程。可以推荐看一下。而不是我的例子,我可以这样做:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    Enforce.Argument(() => subjects);
    return subjects.Count() == 5;
}

10 个答案:

答案 0 :(得分:19)

是的,它会抛出一个ArgumentNullException。我可以想到进行额外检查的两个原因:

  • 如果您稍后返回并在调用subjects.Count()之前更改方法以执行某些操作并忘记在此时进行检查,则可能会在抛出异常之前产生副作用,这不是'好的。
  • 目前,堆栈跟踪将在顶部显示subjects.Count(),并且可能带有source参数名称的消息。对于可以看到HasFive参数名称的subjects来电者来说,这可能会造成混淆。

编辑:只是为了让我不得不再在其他地方写下来了:

subjects.Count()的调用会抛出ArgumentNullException而不是 NullReferenceExceptionCount()是此处的另一种扩展方法,假设正在使用System.Linq.Enumerable中的实现,则会记录(正确)抛出ArgumentNullException。如果你不相信我,试试吧。

编辑:让这更容易......

如果您做了很多这样的检查,您可能希望这样做更简单。我喜欢以下扩展方法:

internal static void ThrowIfNull<T>(this T argument, string name)
    where T : class
{
    if (argument == null)
    {
        throw new ArgumentNullException(name);
    }
}

问题中的示例方法可以变为:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    subjects.ThrowIfNull("subjects");    
    return subjects.Count() == 5;
}

另一种选择是编写一个检查值并将其返回的版本:

internal static T NullGuard<T>(this T argument, string name)
    where T : class
{
    if (argument == null)
    {
        throw new ArgumentNullException(name);
    }
    return argument;
}

然后你可以流利地调用它:

public static bool HasFive<T>(this IEnumerable<T> subjects)
{
    return subjects.NullGuard("subjects").Count() == 5;
}

这对于在构造函数等中复制参数也很有用:

public Person(string name, int age)
{
    this.name = name.NullGuard("name");
    this.age = age;
}

(对于不重要的地方,你可能想要一个没有参数名称的重载。)

答案 1 :(得分:2)

我认为@Jon Skeet绝对是现货,但我想补充以下想法: -

  • 提供有意义的错误消息对于调试,记录和异常报告很有用。 BCL引发的异常不太可能描述代码库WRT异常的具体情况。也许这不是一个问题,空检查(大多数情况下)必然不能给你很多特定领域的信息 - '我意外地传递了一个空,不知道为什么'几乎是你能做的最好的当时,有时您可以提供更多信息,显然在处理其他异常类型时更有可能是相关的。
  • null检查清楚地向其他开发人员和您提供了一种文档形式,如果/当您在一年后回到代码时,可能有人可能会传递null,如果他们这样做了
  • 扩展Jon的优点 - 你可能会在null被提升之前做点什么 - 我认为参与defensive programming非常重要。在运行其他代码之前检查异常是一种防御性编程形式,因为您考虑的事情可能无法按预期的方式工作(或者可能在将来发生您未预期的更改)并确保无论如何发生(假设你的空检查没有被删除)不会出现这样的问题。
  • 这是运行时assert的一种形式,您的参数不为null。你可以继续假设它不是。
  • 上述假设可能会导致代码更加细致,您可以在知道参数不为空的情况下编写其余代码,从而减少无关的后续空值检查。

答案 2 :(得分:1)

在我看来,你应该检查空值。想到两件事。

它明确了运行时可能发生的错误。

它还为您提供了抛出更好的异常而不是泛型ArgumentNullException的机会。因此,使异常的原因更明确。

答案 3 :(得分:1)

您将被抛出的异常将是未设置为对象实例的Object引用。

在追踪问题时,不是最有用的例外。

你拥有它的方式将通过明确声明你的主题参考为空来为你提供更多有用的信息。

答案 4 :(得分:1)

我认为在函数顶部进行前置条件检查是一种很好的做法。也许只是我的代码充满了错误,但这种做法给我带来了很多错误。

此外,如果您从最相关的堆栈帧中获取了带参数名称的ArgumentNullException,则更容易找出问题的根源。此外,函数体中的代码可能会随着时间的推移而发生变化,因此我不会依赖它来捕获前提条件问题。

答案 5 :(得分:1)

它总是取决于背景(在我看来)。

例如,在编写库(供其他人使用)时,完全检查每个参数并抛出适当的异常当然是有意义的。

在编写项目中使用的方法时,我通常会跳过这些检查,试图减小代码库的大小。但即使在这种情况下,也可能存在一个级别(在应用程序层之间),您仍然可以进行此类检查。这取决于背景,项目规模,团队规模......

对于由一个人建造的小项目来说,这当然没有意义:)

答案 6 :(得分:1)

这取决于具体方法。在这种情况下 - 我认为,如果扩展方法可以处理null,则异常不是必需的,并且使用效果会更好。

public static bool HasFive<T>(this IEnumerable<T> subjects) {
    if ( object.ReferenceEquals( subjects, null ) ) { return false; }

    return subjects.Count() == 5;
}

如果您调用“items.HasFive()”并且“items”为空,那么项目不包含五个项目。

但是如果你有扩展方法:

public static T GetFift<T>(this IEnumerable<T> subjects) {
    ...
}

应该调用“subject == null”的异常,因为没有有效的方法,如何处理它。

答案 7 :(得分:0)

如果查看Enumerable类(System.Core.dll)的源代码,其中为IEnumerables类定义了许多默认扩展方法,您可以看到它们都检查带参数的空引用。

public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return SkipIterator<TSource>(source, count);
}

这有点明显,但我倾向于遵循我在基础框架库源代码中找到的内容,因为您知道这很可能是最佳实践。

答案 8 :(得分:0)

是的,有两个原因:

首先,IEnumerable上的其他扩展方法和你的代码的消费者也可以期望你的代码也这样做,但其次,更重要的是,如果你的查询中有一长串运算符,那么知道哪个< / strong>一个抛出异常是有用的信息。

答案 9 :(得分:0)

在我看来,应该检查以后会引发错误的已知条件(至少对于公共方法)。这样就可以更容易地发现问题的根源。

我会提出更多信息性异常,例如:

if (subjects == null)
{
     throw new ArgumentNullException("subjects ", "subjects is null.");
}