使用空合并运算符进行隐式转换

Posted

技术标签:

【中文标题】使用空合并运算符进行隐式转换【英文标题】:Implicit conversion with null-coalescing operator 【发布时间】:2014-02-18 16:12:54 【问题描述】:

我发现我的程序有一个奇怪的行为,经过进一步分析,我发现我的 C# 知识或其他地方可能有问题。我相信这是我的错误,但我无法在任何地方找到答案......

public class B

    public static implicit operator B(A values) 
    
        return null; 
    

public class A  

public class Program

    static void Main(string[] args)
    
        A a = new A();
        B b = a ?? new B();
        //b = null ... is it wrong that I expect b to be B() ?
    

此代码中的变量“b”被评估为 null。我不明白为什么它是空的。

我用谷歌搜索了这个问题 - Implicit casting of Null-Coalescing operator result - 的官方规范。

但是按照这个规范,我找不到“b”为空的原因:(也许我读错了,在这种情况下我为垃圾邮件道歉。

如果 A 存在且不是可空类型或引用类型,则会发生编译时错误。

...不是这样的。

如果 b 是动态表达式,则结果类型是动态的。在运行时,首先评估 a。如果 a 不为 null,则将 a 转换为动态,这将成为结果。否则,对 b 求值,这将成为结果。

...事实并非如此。

否则,如果 A 存在并且是可空类型,并且存在从 b 到 A0 的隐式转换,则结果类型为 A0。在运行时,首先评估 a。如果 a 不为 null,则将 a 解包为类型 A0,这将成为结果。否则,b 被求值并转换为 A0 类型,这就是结果。

...A 存在,不存在从 b 到 A0 的隐式转换。

否则,如果存在 A 并且存在从 b 到 A 的隐式转换,则结果类型为 A。在运行时,首先计算 a。如果 a 不为空,则 a 成为结果。否则,b 被求值并转换为 A 类型,这就是结果。

...A 存在,不存在从 b 到 A 的隐式转换。

否则,如果 b 具有 B 类型并且存在从 a 到 B 的隐式转换,则结果类型为 B。在运行时,首先计算 a。如果 a 不为空,则将 a 解包为 A0 类型(如果 A 存在且可为空)并转换为 B 类型,这将成为结果。否则,b 被评估并成为结果。

...b 具有 B 类型,并且存在从 a 到 B 的隐式转换。 a 被评估为 null。因此,b 应该被评估并且 b 应该是结果。

否则a和b不兼容,会出现编译时错误。 不会发生

请问我有什么遗漏吗?

【问题讨论】:

不应该重写操作符来工作吗? 非常有趣。请注意,如果您将最后一行更改为 B b = (B)a ?? new B();,即您在代码中“显式”编写隐式强制转换,则会有所不同。 @JeppeStigNielsen 你这样做是在强制转换发生在空检查之前,而不是之后。 @Servy 没错。我发现并写在我的答案中。 【参考方案1】:

为什么您希望 null 合并运算符返回 new B()a 不为空,因此 a ?? new B() 的计算结果为 a

既然我们知道将返回a,我们需要确定结果的类型(T)以及是否需要将a 转换为T

• 否则,如果 b 具有 B 类型并且存在从 a 到 B,结果类型为 B。 在运行时,首先评估 a。如果一个 不为空,a 是 解包为 A0 类型(如果 A 存在并且可以为空) 并转化为B型,这就是结果。否则,b 是 评估并成为结果。

存在从AB 的隐式转换,因此B 是表达式的结果类型。这意味着a 将被隐式转换为B。并且您的隐式运算符返回 null

事实上,如果你写var b = a ?? new B();(注意var),你会看到编译器推断B是表达式返回的类型。

【讨论】:

这并不能解释为什么将声明更改为 object b 仍然会产生 null。 @KendallFrey 足够接近它。 ?? 仅在第一个和第二个操作数解析为相同类型(或类型的可空和不可空版本)时才有效。第一个操作数a 与null 进行比较,然后转换为B 类型,此时隐式转换将其转换为null。它根本不需要分配给任何东西。 对于?? 运算符,问题本身中引用的规则比您引用的规则更相关(并且更复杂)(通过 Lipperts 博客)对于三元 ?: 运算符。例如,给定变量 bool b = false; short? s = 10; int i = 10;,表达式 s ?? i 是允许的,而 b ? s : i 则不能编译。这个答案是第一位的,但我认为其他答案更准确。 @JeppeStigNielsen 不错的收获!每当讨论三元和空合并运算符时,我通常会参考该博客文章 - 我倾向于忘记这些规则仅适用于三元运算符(即使两组规则非常相似) .我已经更新了我的答案。【参考方案2】:

否则,如果 b 具有 B 类型并且存在从 a 的隐式转换 对于 B,结果类型为 B。在运行时,首先评估 a。如果一个是 不为空,a 被解包为 A0 类型(如果 A 存在并且可以为空)并且 转换为 B 型,这就是结果。否则,b 是 评估并成为结果。

...b 具有 B 类型,并且存在从 a 到 B 的隐式转换。 a 是 评估为空。因此,b 应该被评估并且 b 应该是 结果。

您对此的解释是错误的。没有说 aB 的转换是在执行 null 检查之前完成的。它指出null 检查是在转换之前完成的!

你的情况很适合:

如果 a 不为 null,则将 a 解包为 A0 类型(如果 A 存在并且是 可以为空)并转换为B型,这成为 结果

【讨论】:

【参考方案3】:

好吧,规范说(我改为xy 以减少混淆):

• 否则,如果 y 的类型为 Y,并且存在从 x 到 Y 的隐式转换,则结果类型为 Y。在运行时,首先计算 x。如果 x 不为空,则 x 被解包为 X0 类型(如果 X 存在并且可以为空)并转换为 Y 类型,这就是结果。否则,对 y 求值并成为结果。

会发生这种情况。首先,检查左侧的x,也就是a,检查null。但它本身并不是null。那么左边的要使用的。然后运行隐式转换。 B 类型的结果是 ... null

请注意,这不同于:

    A a = new A();
    B b = (B)a ?? new B();

在这种情况下,左侧操作数是一个表达式 (x),它本身就是 null,结果变成右侧 (y)。

也许只有当原始类型是null 时,引用类型之间的隐式转换才应该返回null(如果和),作为一种好习惯?


我想编写规范的人可以这样做(但没有):

• 否则,如果 y 具有 Y 类型并且存在从 x 到 Y 的隐式转换,则结果类型为 Y。在运行时,首先计算 x 并将其转换为 Y 类型。如果该转换的输出为不为空,该输出成为结果。否则,评估 y 并成为结果。

也许这样会更直观?无论转换的输入是否为null,它都会强制运行时调用您的隐式转换。如果典型实现很快确定null → null,那应该不会太昂贵。

【讨论】:

【参考方案4】:

我们需要查看的部分是空合并表达式的编译时类型。

否则,如果 b 具有 B 类型并且存在从 a 到 B 的隐式转换,则结果类型为 B。在运行时,首先计算 a。如果 a 不为空,则将 a 解包为 A0 类型(如果 A 存在且可为空)并转换为 B 类型,这将成为结果。否则,b 被评估并成为结果。

将其放入伪代码:

public Tuple<Type, object> NullCoalesce<TA, TB>(TA a, TB b)

    ...
    else if (a is TB) // pseudocode alert, this won't work in actual C#
    
        Type type = typeof(TB);
        object result;
        if (a != null)
        
            result = (TB)a; // in your example, this resolves to null
        
        else
        
            result = b;
        
        return new Tuple<Type, object>(type, result);
    
    ...

【讨论】:

以上是关于使用空合并运算符进行隐式转换的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript:隐式类型转换

JavaScript:隐式类型转换

MySQL隐式转换

MySQL隐式转换

有趣的JavaScript隐式类型转换

有趣的JavaScript隐式类型转换