假设我有这种扩展方法:
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;
}
答案 0 :(得分:19)
是的,它会抛出一个ArgumentNullException
。我可以想到进行额外检查的两个原因:
subjects.Count()
之前更改方法以执行某些操作并忘记在此时进行检查,则可能会在抛出异常之前产生副作用,这不是'好的。subjects.Count()
,并且可能带有source
参数名称的消息。对于可以看到HasFive
参数名称的subjects
来电者来说,这可能会造成混淆。编辑:只是为了让我不得不再在其他地方写下来了:
对subjects.Count()
的调用会抛出ArgumentNullException
,而不是 NullReferenceException
。 Count()
是此处的另一种扩展方法,假设正在使用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绝对是现货,但我想补充以下想法: -
答案 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.");
}