空传播运算符和扩展方法

我一直在看Visual Studio 14 CTP和C#6.0,并使用空传播操作符。

不过,我找不到为什么下面的代码不能编译。 function尚未logging,所以我不确定这是一个错误还是扩展方法根本不支持?. 操作员和错误信息是误导。

 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'接受types'ConsoleApplication1.CC'的第一个参数可以被find(你是否遗漏了使用指令或程序集引用?

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

首先,我不同意SLaks的回答,这是不支持的,因为扩展方法不取消引用他们的this参数。 这是一个毫无根据的说法,因为在任何devise 讨论中都没有提到它。 另外,运算符的语义变成了大致看起来像三元运算符( (obj == null) ? null : obj.Member ),所以它没有一个很好的理由,为什么它不能在技术意义上被支持。 我的意思是,当它归结为生成的代码时,实例方法中隐含的this this与静态扩展方法中的显式无关。

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

错误是,当Binder试图将语法绑定到编译的符号,它使用一个方法private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [链接]这是无法返回所需的方法名称,当它试图绑定调用expression式(我们的方法调用)。

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

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

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

有趣的是,方法名不是为第一个var cr = c?.Get(); 编译。 它的工作,但是,因为本地方法组成员首先被发现的types,并传递给BindInvocationExpression [ 链接 ]的调用。 当方法被parsing时 (注意在尝试ResolveDefaultMethodGroup [ link ]之前调用了BindExtensionMethod [ link ]),它首先检查这些方法并find它。 在扩展方法的情况下,它试图find一个扩展方法,它匹配传入方法的方法名称,在这种情况下,它是一个空string而不是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(); } } 

是。 这是一个错误。 感谢您提出这个问题。 示例应该编译,并应导致Get的条件调用,而不pipeGet是否为扩展名。

“?”的用法 在cc?.Get()是一个迹象表明,调用者希望cc null-checked之前继续进行。 即使Get可以以某种方式处理null,调用者也不希望发生这种情况。

无效传播运算符的要点在于避免对null值进行拒绝。

但是,扩展方法并不考虑它们的this参数,实际上可以在null值上被完美地调用。
(尽pipe该方法本身很可能会抛出一个exception,如果不期望为null

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