空传播运算符和扩展方法 [英] Null propagation operator and extension methods

查看:38
本文介绍了空传播运算符和扩展方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在研究 Visual Studio 14 CTP 和 C# 6.0 并使用空传播运算符.

但是,我找不到以下代码无法编译的原因.这些功能尚未记录在案,因此我不确定这是错误还是扩展方法根本不支持 ?. 运算符,并且错误消息具有误导性.

C 类{公共对象 Get(){返回空;}}CC级{}静态类 CCExtensions{公共静态对象 Get(this CC c){返回空;}}课程计划{static void Main(string[] args){C c = 空;var cr = c?.Get();//这个编译(Get是实例方法)CC cc = 空;var ccr = cc?.Get();//这不会编译Console.ReadLine();}}

错误信息是:

<块引用>

ConsoleApplication1.CC"不包含Get"的定义,并且找不到接受ConsoleApplication1.CC"类型的第一个参数的扩展方法Get"(您是否缺少 using 指令或程序集引用?)

解决方案

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

首先,我不同意 SLaks 的回答,因为扩展方法不会取消引用它们的 this 参数.考虑到 设计 讨论.另外,运算符的语义变成了大致类似于三元运算符 ((obj == null) ? null : obj.Member),所以它真的没有一个很好的理由为什么它不能'技术意义上的支持.我的意思是,当它归结为生成的代码时,实例方法上的隐式 this 和静态扩展方法上的显式 this 确实没有区别.>

错误消息是一个很好的线索,表明这是一个错误,因为它抱怨该方法不存在,而实际上确实存在.您可能已经通过从调用中删除条件运算符、改用成员访问运算符并成功编译代码来测试了这一点.如果这是对运算符的非法使用,您将收到类似于以下内容的消息:error CS0023: Operator '.'不能应用于类型为"的操作数.

错误在于当Binder试图将语法绑定到编译符号时,它使用了一个方法private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [link] 未能返回尝试绑定调用表达式(我们的方法调用)时需要的方法名称.

一个可能的解决方法是在 GetNameSyntax[link] 如下(文件:Compilers/CSharp/Source/Binder/Binder_Expressions.cs:2748):

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

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

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

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

内部类程序{私有静态无效主(字符串 [] args){C c1 = (C) 空;对象 obj1 = c1 != null ?c1.Get() : (对象) null;CC c2 = (CC) 空;对象 obj2 = c2 != null ?CCExtensions.Get(c2) : (对象) null;Console.ReadLine();}}

I've been looking at Visual Studio 14 CTP along with C# 6.0 and playing with the null-propagation operator.

However, I couldn't find why the following code does not compile. The features are not yet documented so I'm not sure whether this is a bug or extension methods simply are not supported with the ?. operator and the error message is misleading.

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();
    }
}

Error message is:

'ConsoleApplication1.CC' does not contain a definition for 'Get' and no extension method 'Get' accepting a first argument of type 'ConsoleApplication1.CC' could be found (are you missing a using directive or an assembly reference?)

解决方案

I don't work on the Roslyn team, but I am fairly confident that this is a bug. I took a look at the source code and I can explain what's happening.

First off, I disagree with SLaks answer that this isn't supported because extension methods do not dereference their this parameter. It is an unfounded claim, considering that there's no mention of it in any of the design discussions. Plus, the semantics of the operator turn into somethat that roughly looks like the ternary operator ((obj == null) ? null : obj.Member), so there's not really a good reason why it couldn't be supported in a technical sense. I mean, when it boils down to generated code, there really is no difference in the implicit this on an instance method and the explicit this on the static extension method.

The error message is a good clue that this is a bug, because it's complaining that the method doesn't exist, when it actually does. You may have tested this by removing the conditional operator from the call, using the member access operator instead, and having the code compile successfully. If this were an illegal use of the operator, you would get a message similar to this: error CS0023: Operator '.' cannot be applied to operand of type '<type>'.

The bug is that when the Binder is trying to bind the syntax to the compiled symbols, it uses a method private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [link] which is failing to return the method name that is needed when it tries to bind the invocation expression (our method call).

A possible fix is to add an extra case statement to the switch in GetNameSyntax[link] as follows (File: Compilers/CSharp/Source/Binder/Binder_Expressions.cs:2748):

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

This was probably overlooked because the syntax for calling extension methods as members, that is using the member access operator) winds up using a different set of syntax than when used with the member access operator vs. the conditional access operator, specifically, the ?. operator uses a MemberBindingExpressionSyntax that wasn't taken into account for that GetNameSyntax method.

Interestingly, the method name is not populated for the first var cr = c?.Get(); which compiles. It works, however, because local method group members are first found for the type and are passed to the call of BindInvocationExpression [link]. When the method is being resolved (note the call to ResolveDefaultMethodGroup [link] before trying to BindExtensionMethod [link]), it first checks those methods and finds it. In the case of the extension method, it tries to find an extension method that matches the method name that was passed into the method, which in this case was an empty string instead of Get, and causes the erroneous error to be displayed.

With my local version of Roslyn with my bug fix, I get a compiled assembly whose code looks like (regenerated using 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();
    }
}

这篇关于空传播运算符和扩展方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆