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

让我们说我有这种扩展方法:

public static bool HasFive(this IEnumerable subjects) { if(subjects == null) throw new ArgumentNullException("subjects"); return subjects.Count() == 5; } 

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

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

无论如何,你们怎么想?


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


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

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

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

  • 如果你以后回去并在调用subjects.Count()之前更改方法以执行某些操作,并且忘记在此时进行检查,则可能会在抛出exception之前产生副作用,这不是很好。
  • 目前,堆栈跟踪将在顶部显示subjects.Count() ,并且可能带有source参数名称的消息。 这可能会让HasFive的调用者感到困惑,他可以查看subjects参数名称。

编辑:只是为了节省我不得不再写在其他地方:

subjects.Count()的调用将抛出ArgumentNullException而不是 NullReferenceExceptionCount()是这里的另一个扩展方法,假设正在使用System.Linq.Enumerable的实现,那么(正确地)记录了抛出ArgumentNullException 。 如果你不相信我,试试吧。

编辑:让这更容易……

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

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

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

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

另一个替代方法是编写一个检查该值并返回它的版本,如下所示:

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

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

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

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

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

(对于不重要的地方,您可能需要没有参数名称的重载。)

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

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

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

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

它还为您提供了抛出更好的exception而不是genericsArgumentNullException的机会。 因此,使exception的原因更明确。

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

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

你拥有它的方式将通过专门声明它是你的主题引用为空来给你更多有用的信息。

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

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

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

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

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

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

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

 public static bool HasFive(this IEnumerable subjects) { if ( object.ReferenceEquals( subjects, null ) ) { return false; } return subjects.Count() == 5; } 

如果您调用“items.HasFive()”并且“items”为null,则表示项目没有五个项目。

但是如果你有扩展方法:

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

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

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

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

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

是的,有两个原因:

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

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

我会提出一个更具信息性的exception,例如:

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