【问题标题】:In C#, what happens when you call an extension method on a null object?在 C# 中,当您在空对象上调用扩展方法时会发生什么?
【发布时间】:2010-10-25 05:39:34
【问题描述】:

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

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

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

【问题讨论】:

标签: c# parameters null extension-methods


【解决方案1】:

这会很好(没有例外)。扩展方法不使用虚拟调用(即它使用“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)) {...}

显然没有空检查。

【讨论】:

  • Marc,您说的是“虚拟”调用——但对实例方法的非虚拟调用也是如此。我认为这里的“虚拟”一词用错了。
  • @Konrad:这取决于上下文。 C# 编译器通常甚至对非虚拟方法也使用 callvirt,正是为了获得空值检查。
  • 我指的是 call 和 callvirt il 指令之间的区别。在一次编辑中,我实际上试图对两个 Opcodes 页面进行 href,但编辑器对链接感到不满...
  • 我看不出这种使用扩展方法有什么用处,真的。仅仅因为它可以完成并不意味着它是正确的,正如下面提到的 Binary Worrier 那样,在我看来,至少可以说它更像是一种失常。
  • @Trap:如果你喜欢函数式编程,这个功能非常棒。
【解决方案2】:

Marc Gravell 正确答案的补充。

如果 this 参数明显为空,您可能会收到编译器的警告:

default(string).MyExtension();

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

【讨论】:

  • 为什么会警告“总是导致 System.NullReferenceException”。事实上,它永远不会?
  • 幸运的是,我们程序员只关心错误,而不关心警告:p
  • @JulianR:是的,有些可以,有些不可以。在我们的发布构建配置中,我们将警告视为错误。所以它只是行不通。
  • 感谢您的来信;我会在 bug 数据库中得到这个,我们会看看我们是否可以为 C# 4.0 修复它。 (没有承诺——因为这是一个不切实际的极端情况,只是一个警告,我们可能会努力修复它。)
  • @Stefan:因为这是一个错误而不是“真正的”警告,您可以使用 #pragma 语句来抑制警告,以使代码通过您的发布版本。
【解决方案3】:

正如您已经发现的那样,由于扩展方法只是被美化的静态方法,它们将通过传入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; 
  } 
}

前段时间我也写过a blog post

【讨论】:

  • 投了赞成票,因为它对我来说是正确且有意义的(而且写得很好),而我也更喜欢@Marc Gravell 的回答中描述的用法。
【解决方案4】:

一个 null 将被传递给扩展方法。

如果方法试图访问对象而不检查它是否为空,那么是的,它会抛出异常。

这里的一个人写了“IsNull”和“IsNotNull”扩展方法来检查引用是否传递为null。就我个人而言,我认为这是一种失常,不应该看到光明,但它是完全有效的 c#。

【讨论】:

  • 确实,对我来说,这就像问一具尸体“你还活着吗”,得到的回答是“不”。尸体不能回答任何问题,你也不应该能够“调用”空对象上的方法。
  • 我不同意 Binary Worrier 的逻辑,因为它可以很方便地调用扩展而不用担心 null refs,但是 +1 用于类比喜剧价值 :-)
  • 其实,有时候你不知道人死了没有,所以你还是问,人可能会回答:“没有,我只是闭着眼睛休息”
  • 当您需要链接多个操作(例如 3+)时,您可以(假设没有副作用)将几行无聊的样板 null 检查代码转换为带有“null”的优雅链接的单行-safe”扩展方法。 (类似于建议的“.?”-操作符,但诚然没有那么优雅。)如果扩展名不明显是“空安全”,我通常在方法前加上“安全”,所以如果它是一个副本-方法,它的名字可以是“SafeCopy”,如果参数为null,它会返回null。
  • 我对@BinaryWorrier 的回答笑得很厉害哈哈哈我看到自己踢了一具尸体来检查它是否死了哈哈哈所以在我的想象中,谁检查了尸体是否死了是我和不是身体本身,检查的实施在我身上,主动踢它看它是否移动。所以一个身体不知道它是否死了,WHO检查,知道,现在你可以争辩说你可以“插入”到身体上,让它告诉你它是否死了,我认为这就是一个扩展是为了。
【解决方案5】:

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

您可以阅读这篇文章作为示例:How to Reduce Cyclomatic Complexity: Guard Clause 短版是这样的:

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

        ...

    }

【讨论】:

    【解决方案6】:

    扩展方法是静态的,所以如果你对这个 MyObject 不做任何事情,这应该不是问题,快速测试应该可以验证它:)

    【讨论】:

      【解决方案7】:

      myObject 为空时,myObject.MyExtensionMethod(); 永远不会抛出空引用异常... 但如果MyExtensionMethod() 不能正确处理空值,它会抛出异常

      https://dotnetfiddle.net/KqwLya

      【讨论】:

        【解决方案8】:

        当你希望你的文章具有可读性和垂直性时,几乎没有什么黄金法则。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-15
          • 1970-01-01
          • 2019-01-13
          • 1970-01-01
          • 2013-06-22
          相关资源
          最近更新 更多