【问题标题】:C# Safe navigation operator - what is actually going on?C# 安全导航运算符 - 实际发生了什么?
【发布时间】:2015-11-22 13:45:40
【问题描述】:

我一直对 C#6 中添加的安全导航运算符功能感兴趣。我已经期待了一段时间了。但我发现一些与我预期不同的行为。我意识到我真的不明白它是如何工作的。

给定这个类

class Foo {
    public int? Measure;
}

这是一些使用 new 运算符的代码。

Foo f = new Foo { Measure = 3 };
Console.WriteLine(f?.Measure);  // 3

f = new Foo { Measure = null };
Console.WriteLine(f?.Measure);  // null

f = null;
Console.WriteLine(f?.Measure);  // null

到这里为止,一切都按预期工作。 ?. 在左侧不为空时访问成员,否则返回空。但这里的事情朝着我没有预料到的方向发展。

var i = f?.Measure; // i is Nullable<int>
Console.WriteLine(i.HasValue); // false
Console.WriteLine(f?.Measure.HasValue); // null

什么?

为什么我可以从i 得到HasValue,但不能从我分配给i 的同一个表达式中得到? HasValue 怎么可能是空的?

编辑:我真正的问题是关于程序行为,而不是编译错误。我删除了关于编译的额外内容,并将这个问题更具体地集中在为什么看似相同的逻辑会返回两个不同的结果。

【问题讨论】:

    标签: c# c#-6.0 null-conditional-operator


    【解决方案1】:

    让我们从逻辑上分析一下。

    var f = ???;
    var i = f?.Measure;
    var t = i.HasValue;
    

    我们不知道f 是否为空。

    1. 如果f null,则结果(i)为null
    2. 如果f 不是 null,则结果 (i) 是 int

    因此,i 定义为int?,而tbool

    现在,让我们来看看这个:

    var f = ???;
    var i = f?.Measure.HasValue;
    
    1. 如果f null,则结果 (i) 为 null
    2. 如果f 不是 null,那么结果(i)是Measure.HasValue,它是一个布尔值。

    因此,ibool?

    如果f 为null,我们短路并返回null。如果不是,我们返回.HasValuebool 结果。

    本质上,当使用?. - 返回类型必须是一个引用值,或者Nullable&lt;T&gt;,因为表达式可以短路返回null。

    【讨论】:

    • 嗯...这对我来说有点令人难以置信,但我认为我可以适应这种行为。这肯定和我想象的不一样。如果它像你说的那样工作(而且看起来确实如此!)那么f?.Measure 不是f?.Measure.HasValue 的“逻辑子表达式”。也许我想了一会儿之后会更喜欢这种方式,但目前看来,这似乎更难推理。
    • 我同意一开始这有点不直观。您可以将表达式解读为“如果 f 为 null,则返回 null。如果 f 不为 null,请告诉我 Measure 是否有值”。只是需要一些时间来适应,我想。要意识到的主要事情是你正在阅读和理解这个:f?.Measure.HasValue,因为这个:(f?.Measure).HasValue,它不是 - 类似于 var i = 1 + 2;var t = i *3;var i = 1 + 2 * 3; 的不同之处
    • 这是一个有用的比较,但在那个例子中,至少2 * 3 有一个值!我会假设最右边的成员访问器不可能首先应用,因为在应用左边的访问器之前没有任何东西可以应用它。换句话说,2 本身就是一个完美的值,但Measure 仅对特定的Foo 有意义。
    • @recursive 也许乘法示例有点偏离 - 因为这种行为与执行顺序无关。它只与结果类型(最右边的成员 - 在本例中为 bool)以及 ?. 的存在有关,这意味着 null可能的 - 所以如果返回类型(bool不可为空,它必须包裹在Nullable&lt;T&gt;中——因为如果fnull,我们需要返回null,但我们不能放在 bool.. 如果 HasValue 返回一个对象 - 甚至是 int?,那么一切都会好起来的,它会像你预期的那样运行
    • @recursive:在此功能的初始实施阶段,我记得看到一些关于此运算符的评估顺序的实质性讨论(我无法找到)。设计师们很清楚他们的决定将导致的混乱......但替代方案最终变得更糟。
    【解决方案2】:
    var i = f?.Measure; // i is Nullable<int>
    Console.WriteLine(i.HasValue); // false
    Console.WriteLine(f?.Measure.HasValue); // null
    

    在这种情况下,f 为空。

    i.HasValue 返回false 的原因是因为i 的类型为Nullable&lt;int&gt;。所以即使i 的值为空,就像在这种情况下,i.HasValue 仍然可以访问。

    但是,f?.Measure.HasValue 在评估 f? 后立即返回 null。因此,您在上面看到了结果。

    引用Rob's comment:

    要意识到的主要事情是您正在阅读和理解 this: f?.Measure.HasValue as this: (f?.Measure).HasValue, 这是 不是。

    【讨论】:

      【解决方案3】:

      Nullable&lt;T&gt; 实际上是一个结构体,因此不能为空,只有它的Value 可以,所以HasValue 总是可以访问的。

      【讨论】:

      • 当然,它是可访问的,正如我在问题中所展示的那样。然而,它的值却神秘地从false 变为null
      【解决方案4】:

      我今天遇到了这个。

      下面的 C# sn-p 打印什么?

      public class NullTests
      {
          public static void Main(string[] args)
          {
              object obj = DoIt();
              Console.WriteLine(obj?.ToString().NullToNothing());
          }
      
          private static object DoIt() => null;
      }
      
      public static class Extensions
      {
          public static string NullToNothing(this string input) => input ?? "nothing";
      }
      

      答案:null

      以下 Kotlin sn-p 打印什么?

      fun main() {
          val obj = doIt()
          println(obj?.toString().NullToNothing())
      }
      
      fun doIt() = null
      fun String?.NullToNothing() = this ?: "nothing"
      

      答案:“什么都没有”

      和你一样,我期待 Kotlin 的行为,这让我在一天中的大部分时间里都被绊倒了。 :(

      【讨论】:

        猜你喜欢
        • 2017-05-27
        • 2011-03-31
        • 2017-12-22
        • 2011-05-05
        • 2016-04-07
        • 2018-02-22
        • 1970-01-01
        • 2012-03-13
        • 1970-01-01
        相关资源
        最近更新 更多