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

tpo*_*wer 310 c# parameters null extension-methods

是使用null值调用方法还是提供null引用异常?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*ell 369

这样可以正常工作(也不例外).扩展方法不使用虚拟调用(即它使用"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);
}
Run Code Online (Sandbox Code Playgroud)

等等

从根本上说,对静态调用的调用是非常直接的 - 即

string s = ...
if(s.IsNullOrEmpty()) {...}
Run Code Online (Sandbox Code Playgroud)

变为:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}
Run Code Online (Sandbox Code Playgroud)

哪里显然没有空检查.

  • @Konrad:这取决于具体情况.C#编译器通常甚至对非虚方法使用callvirt,正是为了获得空检查. (6认同)
  • @陷阱:如果您正在使用函数式编程,则此功能非常有用。 (3认同)
  • 我不知道这种扩展方法的使用真的有什么用。仅仅因为可以做到并不意味着它是对的,正如下面的“二进制担忧者”所提到的,在我看来,至少可以说这像是一种畸变。 (2认同)

Ste*_*ger 48

除了Marc Gravell的正确答案.

如果显然this参数为null,您可以从编译器收到警告:

default(string).MyExtension();
Run Code Online (Sandbox Code Playgroud)

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

  • 幸运的是,我们程序员只关心错误,而不是警告:p (42认同)
  • 为什么它会警告"总是导致System.NullReferenceException".事实上,它永远不会? (28认同)
  • 谢谢你的说明; 我将在bug数据库中得到这个,我们将看看我们是否可以为C#4.0修复它.(没有承诺 - 因为这是一个不切实际的角落案件,仅仅是一个警告,我们可能会试图解决它.) (7认同)
  • @JulianR:是的,有些人有,有些则没有.在我们的发布版本配置中,我们将警告视为错误.所以它不起作用. (6认同)
  • @Stefan:由于它是一个错误而不是"真实"警告,您可以使用#pragma语句来抑制警告以使代码通过您的发布版本. (3认同)
  • @tpower:肯定是因为此检查从未更新过正确处理扩展方法.当我尝试调用实际上只需要参数类型的扩展方法时,我发现它,但我没有实例.现在我必须调用一个更长的静态方法. (2认同)
  • 这已被[报道](http://connect.microsoft.com/VisualStudio/feedback/details/695362/c-compiler-gives-incorrect-warning-expression-will-always-cause-a-system-nullreferenceexception-for -extension-方法).MS接受这种怪异,但是说修复它并不是他们的首要任务. (2认同)
  • @nawfal:好吧,我现在无能为力!如果您确实希望修复此警告,请向 C# 编译器团队发送 Bug。 (2认同)

Jor*_*dão 22

正如您已经发现的那样,因为扩展方法只是美化的静态方法,所以它们将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; 
  } 
}
Run Code Online (Sandbox Code Playgroud)

我前段时间也写了一篇博文.

  • 投票了,因为它是正确的,对我有意义(并且写得很好),而我也更喜欢@Marc Gravell在回答中描述的用法. (3认同)

Bin*_*ier 16

null将被传递给扩展方法.

如果该方法试图访问该对象而不检查它是否为null,则是,它将引发异常.

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

  • 事实上,对我而言,就像问一具尸体"你还活着"并得到一个"不"的回答.尸体无法回答任何问题,您也不能"调用"空对象上的方法. (18认同)
  • 我不同意Binary Worrier的逻辑,因为它可以方便地调用扩展而不用担心空引用,但+1类比喜剧价值:-) (12认同)
  • 实际上,有时你不知道是否有人死了,所以你仍然会问,这个人可能会回答:"不,只是闭着眼睛休息" (10认同)
  • 当您需要链接多个操作(比如说3+)时,您可以(假设没有副作用)将几行无聊的样板空检查代码转换为具有"空安全"扩展方法的优雅链式单行程.(类似于建议的".?" - 运算符,但诚然不那么优雅.)如果不明显扩展是"无效",我通常在方法前加上"安全",所以如果它是一个副本 - 方法,它的名称可以是"SafeCopy",如果参数为null,它将返回null. (7认同)
  • 我用@BinaryWorrier回答哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈!检查在我身上,积极地踢它,看看它是否移动.因此,身体不知道它是否已经死亡,世卫组织检查,知道,现在你可以争辩说,你可以"插入"身体,告诉你它是否已经死亡,而我认为是什么延期是为了. (3认同)

Zor*_*vat 7

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

您可以阅读本文的示例:如何减少Cyclomatic复杂性:Guard子句短版本是这样的:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

((string)null).AssertNonEmpty("null");
Run Code Online (Sandbox Code Playgroud)

调用工作正常,因为运行时将成功调用null引用上的扩展方法.然后你可以使用这个扩展方法来实现guard子句而不会出现凌乱的语法:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

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

        ...

    }
Run Code Online (Sandbox Code Playgroud)