【问题标题】: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 没错。我发现并写在我的答案中。

标签: c# null implicit-conversion


【解决方案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);
          }
          ...
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-09-09
        • 1970-01-01
        • 1970-01-01
        • 2011-03-03
        • 2016-04-30
        • 2017-03-23
        • 2010-10-27
        • 2019-01-08
        相关资源
        最近更新 更多