In C#, what happens when you call an extension method on a null object?
该方法是用空值调用的还是给出空引用异常?
1 2
| MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception? |
如果是这种情况,我将永远不需要检查我的"this"参数是否为空?
- 当然,除非您处理的是ASP.NET MVC,否则它将抛出这个错误Cannot perform runtime binding on a null reference。
- 相关:来自扩展方法的ArgumentNullException或NullReferenceException?
那就行了(不例外)。扩展方法不使用虚拟调用(即它使用"call"il指令,而不是"callvirt"),因此没有空检查,除非您自己在扩展方法中编写它。在一些情况下,这实际上是有用的:
1 2 3 4 5 6 7 8 9
| public static bool IsNullOrEmpty (this string value)
{
return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull <T >(this T obj, string parameterName )
where T : class
{
if(obj == null) throw new ArgumentNullException (parameterName );
} |
等
从根本上讲,对静态调用的调用是非常字面化的,即
1 2
| string s = ...
if(s.IsNullOrEmpty()) {...} |
号
变成:
1 2
| string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...} |
这里显然没有空校验。
- Marc,你说的是"虚拟"调用——但对实例方法的非虚拟调用也是如此。我觉得"虚拟"这个词放错地方了。
- @康拉德:这取决于上下文。C编译器通常使用callvirt,即使对于非虚拟方法,也会精确地获取空检查。
- 我指的是callvirt-il指令和callvirt-il指令之间的区别。在一次编辑中,我实际上试图给这两个操作码页面加上一个链接,但编辑却在链接处加了条。
- 我不知道这种扩展方法的使用是如何有用的,真的。仅仅因为它是可以做到的并不意味着它是正确的,正如下面提到的二元担忧者,它在我看来更像是一种失常,至少可以这么说。
- 正如他们所说,YMMV
- @trap:如果你喜欢函数式的编程,这个特性是很好的。
- @陷阱:我断言应该有一种方法来指定非虚拟成员应该可以在空对象上调用,特别是对于应该具有值语义(例如字符串)的不可变类型。在.NET之前的系统中,字符串数组默认为保留空字符串(因为空指针是空字符串的可接受表示)。如果字符串上的非虚拟方法可以用于空字符串,那将使许多代码更干净。
- (删除我以前的注释,因为我从扩展方法中找到了相关的问题argumentNullException或NullReferenceException?).
除了马克·格雷威尔的正确答案。
如果很明显此参数为空,则编译器可能会发出警告:
1
| default(string).MyExtension(); |
。
运行时运行良好,但会产生警告"Expression will always cause a System.NullReferenceException, because the default value of string is null"。
- 为什么它会警告"总是导致System.NullReferenceException"。当事实上,它永远不会?
- @tpower:当然,因为这个检查从未被更新以正确处理扩展方法。当我试图调用一个扩展方法时,发现它实际上只需要参数的类型,但我没有实例。现在我必须调用一个静态方法,它要长得多。
- 幸运的是,我们程序员只关心错误,而不关心警告:p
- @朱利安:是的,有的是,有的不是。在我们的版本构建配置中,我们将警告视为错误。所以它就是不起作用。
- 谢谢你的留言;我会在bug数据库中找到这个,我们会看看是否能为C 4.0修复它。(没有承诺——因为这是一个不切实际的角落案件,仅仅是一个警告,我们可能会花点时间来解决它。)
- @斯特凡:因为这是一个bug,而不是一个"真的"警告,所以您可以使用pragma语句来抑制该警告,以使代码通过您的发布版本。
- @马丁:是的,但这没有道理。扩展方法只用于更好的语法。如果它需要一个实用主义,它是所有的鸟类。
- 已经报告过了。微软接受了这种奇怪,但表示解决这一问题并非他们的首要任务。
- @在这种情况下,Ericlippert也考虑表达式树。与Expression> e = () => default(Person).ToString());一样,您也会收到同样的警告,除非表达式树没有编译(但只分析了),这不是问题。这两者都存在于vs 2012编译器中。
- @纳法尔:我现在无能为力了!如果您真的希望修复此警告,请对C编译器团队进行调试。
正如您已经发现的那样,由于扩展方法只是美化静态方法,因此将使用传入的null引用调用它们,而不抛出NullReferenceException。但是,对于调用者来说,它们看起来像实例方法,因此它们也应该这样工作。然后,大多数情况下,您应该检查this参数,如果是null,则抛出异常。如果方法显式地处理null值,并且它的名称适时地表示它,如下面的示例中所示,则可以不这样做:
1 2 3 4 5 6 7 8
| public static class StringNullExtensions {
public static bool IsNullOrEmpty(this string s) {
return string.IsNullOrEmpty(s);
}
public static bool IsNullOrBlank(this string s) {
return s == null || s.Trim().Length == 0;
}
} |
我以前也写过一篇关于这个的博客文章。
- 投票通过是因为它是正确的,对我来说是有意义的(并且写得很好),而我也更喜欢@marc gravell在回答中描述的用法。
将向扩展方法传递空值。
如果该方法试图在不检查是否为空的情况下访问对象,那么是的,它将引发异常。
这里有个人写了"isNull"和"isNotNull"扩展方法,检查是否传递了空引用。我个人认为这是一种反常现象,不应该看到白昼之光,但这是完全正确的。
- 我同意你的看法,这只会导致混乱。
- 事实上,对我来说,这就像问一具尸体"你还活着"并得到"不"的回答。僵尸不能对任何问题作出响应,您也不能对空对象"调用"方法。
- Ruby做了一些类似于.nil的事情?-我个人认为没有什么问题。它不会损害可读性,并且可以使其更具可读性-例如,它是完全无需的或任何其他期望组合。
- 我不同意二进制worrier的逻辑,因为它可以方便地调用扩展而不必担心空引用,但是对于类比喜剧值+1:-)
- @binaryworrier:问题是,假设Foo是一个类类型,那么Foo.Something()是否应该无条件地被视为对Foo持有引用的对象的操作,或者它是否可以是对Foo本身的操作。不幸的是,C没有像C那样的单独的->和.操作符,因为这会让事情变得更清楚。
- 是的,你可以随心所欲地为它辩护,它仍然是让我的括约肌抽搐的东西,而且也不是一个好办法。
- 实际上,有时候你不知道是否有人死了,所以你还是问他,他可能会回答:"不,闭上眼睛休息。"
- 当您需要链接多个操作(比如3+)时,您可以(假设没有副作用)使用"空安全"扩展方法将多行无聊的样板空检查代码转换成一个优雅的链接单行程序。(类似于建议的"."?-运算符,但不必那么优雅。)如果扩展名不明显是"null-safe",我通常在方法前面加上"safe",因此,例如,如果它是一个copy方法,它的名称可以是"safecopy",如果参数为空,它将返回空值。
- 我笑得很厉害,@binaryworrier回答哈哈哈,我看到自己在踢一具尸体来检查它是否死了。哈哈哈,在我的想象中,是谁在检查尸体是否死了,而不是尸体本身,检查的实现在我身上,主动踢它看它是否移动。所以一个身体不知道它是否死了,谁检查,知道,现在你可以争辩说,你可以"插入"到身体,一种方式,让它告诉你它是否死了,在我看来,这是什么扩展。
正如其他人指出的,对空引用调用扩展方法会导致此参数为空,并且不会发生其他特殊情况。这就提出了使用扩展方法来编写保护子句的想法。
您可以阅读本文中的示例:如何减少循环复杂性:guard子句的简短版本如下:
1 2 3 4 5 6 7 8
| public static class StringExtensions
{
public static void AssertNonEmpty (this string value, string paramName )
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException ("Value must be a non-empty string.", paramName );
}
} |
。
这是可以对空引用调用的字符串类扩展方法:
1
| ((string)null).AssertNonEmpty("null"); |
。
调用工作正常,因为运行时将在空引用上成功调用扩展方法。然后,您可以使用此扩展方法实现无语法混乱的guard子句:
1 2 3 4 5 6 7 8 9
| public IRegisteredUser RegisterUser(string userName, string referrerName)
{
userName.AssertNonEmpty("userName");
referrerName.AssertNonEmpty("referrerName");
...
} |
extensionmethod是静态的,因此如果您对这个myObject不做任何操作,它不应该是一个问题,快速测试应该验证它:)
当你想在你的文章中保持可读性和垂直性时,很少有黄金法则。
- 埃菲尔的一句名言是,封装到方法中的特定代码应该对某些输入有效,如果满足某些先决条件并确保预期的输出,则代码是可行的。
在你的情况下-DesignByContract已损坏…您将在一个空实例上执行一些逻辑。