空传播运算符和扩展方法
我一直在看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
)
因此,不清楚一个空安全的扩展方法调用是否会跳过这个调用。