我不在 Roslyn 团队工作,但我相当确信这是一个错误。我看了一下源代码,我可以解释发生了什么。
首先,我不同意 SLaks 的回答,即不支持此功能,因为扩展方法不会取消引用它们的 this 参数。这是一个毫无根据的说法,考虑到thedesigndiscussions 中的任何一个都没有提到它。另外,运算符的语义变成了大致看起来像三元运算符 ((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] 方法,该方法无法返回尝试绑定时所需的方法名称调用表达式(我们的方法调用)。
一种可能的解决方法是在GetNameSyntax[link] 的开关中添加额外的case 语句,如下所示(文件:Compilers/CSharp/Source/Binder/Binder_Expressions.cs:2748):
// ...
case SyntaxKind.MemberBindingExpression:
return ((MemberBindingExpressionSyntax)syntax).Name;
// ...
这可能被忽略了,因为将扩展方法作为成员调用的语法,即使用成员访问运算符)最终使用了一组与成员访问运算符与条件访问运算符不同的语法,具体来说, ?. 运算符使用了 MemberBindingExpressionSyntax,而 GetNameSyntax 方法没有考虑到该 MemberBindingExpressionSyntax。
有趣的是,第一个编译的var cr = c?.Get(); 没有填充方法名称。但是,它可以工作,因为首先为该类型找到本地方法组成员,然后将其传递给BindInvocationExpression [link] 的调用。当方法is being resolved(注意在尝试BindExtensionMethod [link]之前调用ResolveDefaultMethodGroup [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();
}
}