繁体   English   中英

空传播算子和扩展方法

[英]Null propagation operator and extension methods

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

但是,我找不到以下代码为何无法编译的原因。 这些功能尚未记录,因此我不确定这是否是bug或扩展方法?. 操作符,并且错误消息具有误导性。

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'接受类型为'ConsoleApplication1.CC'的第一个参数(您是否缺少using指令或程序集引用?)

是。 这是一个错误。 感谢您提出来。 该示例应该进行编译,并且无论Get是否是扩展,都应导致有条件地调用Get。

“?”的用法 cc?.Get()中的指示表示呼叫者希望在继续进行之前对cc进行空检查。 即使Get可以以某种方式处理null,调用方也不希望这种情况发生。

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

首先,我不同意SLaks的回答,即不支持此操作,因为扩展方法不会取消引用其this参数。 这是一个毫无根据的要求,考虑到有任何的没有提到它 设计 讨论 另外,运算符的语义变成了大致类似于三元运算符( (obj == null) ? null : obj.Member )的(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) [link] ,该方法在尝试执行以下操作时无法返回所需的方法名称绑定调用表达式(我们的方法调用)。

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

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

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

有趣的是,没有为第一个var cr = c?.Get();填充方法名称var cr = c?.Get(); 编译。 但是,它有效,因为首先为该类型找到了本地方法组成员,并将其传递给BindInvocationExpression [ link ]的调用。 解析方法后(请注意在尝试ResolveDefaultMethodGroup [ link ]之前先调用BindExtensionMethod [ 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();
    }
}

空传播运算符的作用是避免取消引用null值。

但是,扩展方法不会遵从它们的this参数,并且实际上可以将其称为null值。
(尽管方法本身如果不期望为null则很可能引发异常)

因此,不清楚是否可以使用null安全扩展方法调用来跳过该调用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM