空传播算子和扩展方法

nos*_*lan 35 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

错误信息是:

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

VSa*_*dov 29

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

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

  • 我完全同意这是一个错误.我应该提到这是"官方"的答案,因为@VSadov是在Roslyn中实现此功能的人. (4认同)
  • 这已在更改中修复http://roslyn.codeplex.com/SourceControl/changeset/345430ed2dfce55bdfe71dc8754cace33ad242be (4认同)

Chr*_*ens 26

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

首先,我不同意SLaks的回答,这是不支持的,因为扩展方法不会取消引用它们的this参数.这是一个毫无根据的要求,考虑到有任何的没有提到它 设计 讨论.另外,运算符的语义变成了大致看起来像三元运算符((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]无法返回尝试绑定调用表达式(我们的方法调用)时所需的方法名称.

可能的解决方法是caseGetNameSyntax[link]中为交换机添加一个额外的语句,如下所示(File:Compilers/CSharp/Source/Binder/Binder_Expressions.cs:2748):

// ...
case SyntaxKind.MemberBindingExpression:
     return ((MemberBindingExpressionSyntax)syntax).Name;
// ...
Run Code Online (Sandbox Code Playgroud)

这可能被忽略了,因为调用扩展方法作为成员的语法(即使用成员访问运算符)使用与成员访问运算符和条件访问运算符一起使用的不同语法集,特别是?.运算符使用a MemberBindingExpressionSyntax不考虑该GetNameSyntax方法.

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

使用我的本地版本的Roslyn修复了我的bug,我得到了一个编译的程序集,其代码看起来像(使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)