在C#中,当您对空对象调用扩展方法时会发生什么?


328

该方法是使用空值调用还是给出空引用异常?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

如果是这种情况,我将永远不需要检查我的'this'参数是否为null?


当然,除非您要使用ASP.NET MVC,否则它将引发此错误Cannot perform runtime binding on a null reference
Mrchief 2015年

Answers:


386

这样就可以正常工作(也不例外)。扩展方法不使用虚拟调用(即,它使用“ call” il指令,而不是“ callvirt”),因此除非您在扩展方法中自己编写,否则不存在空检查。实际上,在某些情况下这很有用:

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);
}

等等

从根本上讲,对静态调用的调用非常直观-即

string s = ...
if(s.IsNullOrEmpty()) {...}

变成:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

显然没有空检查的地方。


1
Marc,您所谈论的是“虚拟”调用-但对于实例方法的非虚拟调用也是如此。我认为这里的“虚拟”一词放错了地方。
康拉德·鲁道夫

6
@Konrad:这取决于上下文。C#编译器通常甚至对非虚拟方法也使用callvirt,以精确地获取空检查。
乔恩·斯基特

我指的是call和callvirt il指令之间的区别。在一次编辑中,我实际上尝试对两个Opcodes页面进行href,但是编辑器对链接bar之以鼻
Marc Gravell

2
我不知道这种扩展方法的使用真的有什么用。仅仅因为可以做到并不意味着它是对的,正如下面的“二进制担忧者”所提到的,在我看来,至少可以说这像是一种畸变。
陷阱

3
@陷阱:如果您正在使用函数式编程,则此功能非常有用。
罗伊·廷克

50

马克·格雷夫(Marc Gravell)提供了正确答案。

如果显而易见此参数为null,则可以从编译器获得警告:

default(string).MyExtension();

在运行时效果很好,但是会产生警告"Expression will always cause a System.NullReferenceException, because the default value of string is null"


32
为什么会警告“总是导致System.NullReferenceException”。事实上,它永远不会吗?
tpower

46
幸运的是,我们程序员只关心错误,而不是警告:p
JulianR

7
@JulianR:是的,有些是,有些不是。在发行版本配置中,我们将警告视为错误。所以它根本不起作用。
Stefan Steinegger,2009年

8
感谢您的来信;我将在错误数据库中找到它,然后看看是否可以针对C#4.0进行修复。(没有保证-因为这是一个不切实际的极端情况,只是一个警告,我们可能会修改它。)
埃里克·利珀特

3
@Stefan:由于这是一个错误而不是“ true”警告,因此您可以使用#pragma语句代替该警告,以使代码通过发行版本。
马丁RL 2010年

24

正如您已经发现的那样,由于扩展方法只是经过修饰的静态方法,因此它们将null通过传入的引用进行调用,而不会NullReferenceException被引发。但是,因为它们看起来像实例方法来调用者,他们也应该表现得如此。然后,大多数时候,您应该检查this参数并抛出异常(如果是)null。如果该方法显式地处理null值并且其名称正确指示了值,则不要执行此操作,如以下示例中所示:

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

不久前,我也写了一篇有关此的博客文章


3
之所以投票,是因为它是正确的并且对我有意义(并且写得很好),而我也更喜欢@Marc Gravell在答复中描述的用法。
qxotk

17

空值将传递给扩展方法。

如果该方法尝试访问该对象而不检查它是否为null,则是,它将抛出异常。

一个人在这里编写了“ IsNull”和“ IsNotNull”扩展方法,用于检查引用是否传递为null。我个人认为这是一种畸变,不应该过时,但这是完全有效的C#。


18
确实,对我来说,这就像问一具尸体“你还活着”,而得到的答案是“否”。尸体无法回答任何问题,也不能在空对象上“调用”方法。
Binary Worrier

14
我不同意Binary Worrier的逻辑,因为它可以方便地调用分机而不用担心空引用,但是类比喜剧价值为+1 :-)
Tim Abell

10
实际上,有时您不知道有人是否死了,所以您仍然要问,这个人可能会回答:“不,只是闭着眼睛休息”
nurchi 2014年

7
当您需要链接多个操作(例如3次以上)时,您可以(假设没有副作用)将几行无聊的样板空检查代码转换为具有“空安全”扩展方法的优美链接的单行代码。(类似于建议的“。?”运算符,但是公认的不那么优雅。)如果扩展名不是“ null-safe”,我通常会在方法前加上“ Safe”作为前缀,例如,如果它是一个副本,方法,其名称可以是“ SafeCopy”,如果参数为null,则它将返回null。
AnorZaken 2014年

3
我用@BinaryWorrier的答案笑得如此哈哈哈哈哈哈哈哈,我发现自己踢了一个尸体以检查它是否已经死了哈哈哈所以在我的想象中,谁检查了尸体是否已经死了是我而不是尸体本身,检查在我里面,主动踢它看看它是否移动。因此,一个尸体不知道它是否已经死亡,世卫组织检查,知道,现在您可以辩称,您可以“插入”该尸体,以一种方式告诉您它是否死亡,而我认为这是什么扩展名。
Zorkind

7

正如其他人指出的那样,在null引用上调用扩展方法会使this参数为null,并且不会发生其他特殊情况。这引起了使用扩展方法编写保护子句的想法。

您可以阅读以下示例文章:如何降低环复杂性:保护子句简写为:

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);
    }
}

这是可以在空引用上调用的字符串类扩展方法:

((string)null).AssertNonEmpty("null");

该调用只能正常运行,因为运行时将在空引用上成功调用扩展方法。然后,您可以使用此扩展方法来实现保护子句,而无需使用混乱的语法:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }

3

该扩展方法是静态的,因此,如果您对此MyObject没有任何要求,那应该不是问题,快速测试应该可以对其进行验证:)


-1

当您希望自己具有可读性和垂直性时,很少有黄金法则。

  • 值得一提的是,艾菲尔说,封装到方法中的特定代码应针对某些输入起作用,如果满足某些先决条件并确保预期输出,则该代码是可行的

在您的情况下-DesignByContract已损坏...您将对空实例执行一些逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.