简体   繁体   English

空传播算子和扩展方法

[英]Null propagation operator and extension methods

I've been looking at Visual Studio 14 CTP along with C# 6.0 and playing with the null-propagation operator. 我一直在研究Visual Studio 14 CTP和C#6.0,并使用null传播运算符。

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 ?. 这些功能尚未记录,因此我不确定这是否是bug或扩展方法?. 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?) 'ConsoleApplication1.CC'不包含'Get'的定义,也找不到扩展方法'Get'接受类型为'ConsoleApplication1.CC'的第一个参数(您是否缺少using指令或程序集引用?)

Yes. 是。 This is a bug. 这是一个错误。 Thanks for bringing this up. 感谢您提出来。 The sample is supposed to compile and should result in a conditional invocation of Get regardless if Get is an extension or not. 该示例应该进行编译,并且无论Get是否是扩展,都应导致有条件地调用Get。

Usage of "?." “?”的用法 in cc?.Get() is an indication that caller wants cc null-checked before proceeding further. cc?.Get()中的指示表示呼叫者希望在继续进行之前对cc进行空检查。 Even if Get could handle null somehow, caller does not want that to happen. 即使Get可以以某种方式处理null,调用方也不希望这种情况发生。

I don't work on the Roslyn team, but I am fairly confident that this is a bug. 我不在罗斯林(Roslyn)团队工作,但我相当有信心这是一个错误。 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. 首先,我不同意SLaks的回答,即不支持此操作,因为扩展方法不会取消引用其this参数。 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. 另外,运算符的语义变成了大致类似于三元运算符( (obj == null) ? null : obj.Member )的(obj == null) ? null : obj.Member ,因此,从技术角度上不能支持它并不是一个很好的理由。 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. 我的意思是,当归结为生成的代码时,实例方法上的隐式this和静态扩展方法上的显式this确实没有区别。

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>' 如果这是对操作员的非法使用,您将收到类似以下消息: error CS0023: Operator '.' cannot be applied to operand of type '<type>' error CS0023: Operator '.' cannot be applied to operand of type '<type>' . 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). 错误是,当Binder试图将语法绑定到已编译符号时,它使用方法private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [link] ,该方法在尝试执行以下操作时无法返回所需的方法名称绑定调用表达式(我们的方法调用)。

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 ): 可能的解决方法是在GetNameSyntax [link]中向开关添加一个额外的case语句,如下所示(文件: 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. 操作员使用MemberBindingExpressionSyntax这是没有考虑到该GetNameSyntax方法。

Interestingly, the method name is not populated for the first var cr = c?.Get(); 有趣的是,没有为第一个var cr = c?.Get();填充方法名称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 ]. 但是,它有效,因为首先为该类型找到了本地方法组成员,并将其传递给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. 解析方法后(请注意在尝试ResolveDefaultMethodGroup [ link ]之前先调用BindExtensionMethod [ link ]),它首先检查这些方法并找到它。 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. 对于扩展方法,它将尝试查找与传递给该方法的方法名称匹配的扩展方法,在这种情况下,该方法名称是空字符串而不是Get ,并导致显示错误消息。

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

The point of the null-propagation operator is to avoid derefencing a null value. 空传播运算符的作用是避免取消引用null值。

However, extension methods do not deference their this parameter, and in fact can be called perfectly fine on null values. 但是,扩展方法不会遵从它们的this参数,并且实际上可以将其称为null值。
(although the method itself is likely to throw an exception if it doesn't expect null ) (尽管方法本身如果不期望为null则很可能引发异常)

Therefore, it would be unclear whether a null-safe extension method call would skip the call or not. 因此,不清楚是否可以使用null安全扩展方法调用来跳过该调用。

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

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