空传播算子和扩展方法

时间:2014-06-10 18:25:10

标签: c# roslyn c#-6.0

我一直在研究Visual Studio 14 CTP和C#6.0以及使用零传播运算符。

但是,我找不到为什么以下代码无法编译。这些功能尚未记录,因此我不确定这是一个错误还是扩展方法只是?.运算符不支持,错误消息具有误导性。

class C
{
    public object Get()
    {
        return null;
    }
}

class CC
{
}

static class CCExtensions
{
    public static object Get(this CC c)
    {
        return null;
    }
}

class Program
{
    static void Main(string[] args)
    {
        C c = null;
        var cr = c?.Get();   //this compiles (Get is instance method)

        CC cc = null;
        var ccr = cc?.Get(); //this doesn't compile

        Console.ReadLine();
    }
}

错误信息是:

  

'ConsoleApplication1.CC'不包含'Get'的定义,也没有扩展方法'Get'接受类型'ConsoleApplication1.CC'的第一个参数(你是否缺少using指令或汇编引用? )

3 个答案:

答案 0 :(得分:29)

是。这是一个错误。谢谢你提出这个问题。 该示例应该编译,并且应该导致条件调用Get,无论Get是否是扩展名。

“?”的用法在cc?.Get()中表示调用者希望在进一步继续之前进行cc null检查。即使Get能以某种方式处理null,调用者也不希望这种情况发生。

答案 1 :(得分:26)

我不在罗斯林团队工作,但我相信这是一个错误。我查看了源代码,我可以解释发生了什么。

首先,我不同意SLaks的回答,即不支持此功能,因为扩展方法不会取消引用其this参数。考虑到在the design discussions中没有任何提及,这是一个毫无根据的主张。此外,运算符的语义变成了大致看起来像三元运算符((obj == null) ? null : obj.Member)的某些语义,因此,从技术意义上说它不能得到支持并不是一个很好的理由。我的意思是,当它归结为生成的代码时,实例方法上的隐式this和静态扩展方法上的显式this确实没有区别。

错误信息是一个很好的线索,这是一个错误,因为它抱怨该方法实际上并不存在。您可能已经通过从调用中删除条件运算符,使用成员访问运算符,并使代码编译成功来测试了这一点。如果这是非法使用运营商,您将收到类似这样的消息:error CS0023: Operator '.' cannot be applied to operand of type '<type>'

错误是当Binder尝试将语法绑定到已编译的符号时,它使用的方法private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [link]无法返回所需的方法名称当它试图绑定调用表达式时(我们的方法调用)。

可能的解决方法是在case [link]中向交换机添加额外的GetNameSyntax语句,如下所示(文件:Compilers/CSharp/Source/Binder/Binder_Expressions.cs:2748):

// ...
case SyntaxKind.MemberBindingExpression:
     return ((MemberBindingExpressionSyntax)syntax).Name;
// ...

这可能被忽略了,因为调用扩展方法作为成员的语法(使用成员访问运算符)使用的语法集与使用成员访问运算符和条件访问运算符时的语法不同,具体而言, ?.运算符使用MemberBindingExpressionSyntax方法未考虑的GetNameSyntax

有趣的是,没有为编译的第一个var cr = c?.Get();填充方法名称。但是,它起作用,因为首先找到该类型的本地方法组成员,并将其传递给BindInvocationExpression [link]的调用。当方法is being resolved(在尝试ResolveDefaultMethodGroup [link]之前记下对BindExtensionMethod [link]的调用)时,它首先检查这些方法并找到它。在扩展方法的情况下,它尝试查找与传递给方法的方法名称匹配的扩展方法,在这种情况下,该方法名称是空字符串而不是Get,并导致错误的错误显示。

使用我的本地版本的Roslyn修复了我的错误,我得到了一个编译的程序集,其代码看起来像(使用dotPeek重新生成):

internal class Program
{
    private static void Main(string[] args)
    {
        C c1 = (C) null;
        object obj1 = c1 != null ? c1.Get() : (object) null;
        CC c2 = (CC) null;
        object obj2 = c2 != null ? CCExtensions.Get(c2) : (object) null;
        Console.ReadLine();
    }
}

答案 2 :(得分:4)

零传播运算符的要点是避免去除null值。

但是,扩展方法不会推导其this参数,实际上可以在null值上完全正常调用。
(尽管如果不期望null

,该方法本身可能会抛出异常

因此,不清楚空值安全的扩展方法调用是否会跳过该调用。