Null 条件和 ToString 一起给出意想不到的结果

Null conditional and ToString together give unexpected results

我有 2 个使用空条件 (?) 运算符并对结果执行 ToString 的语句。这 2 条语句似乎应该有相同的结果,但事实并非如此。唯一不同的是一个有括号,一个没有。

using System.Diagnostics;
using System.Net;

namespace ByPermutationConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            SomeClass someClass = default(SomeClass);

            // Why do these evaluate differently?
            //
            // (someClass?.StatusCode).ToString() is equal to an empty string
            //
            // someClass?.StatusCode.ToString() is equal to null
            //
        }
    }

    public class SomeClass
    {
        public HttpStatusCode StatusCode { get; set; }
    }
}

我希望这 2 个语句的计算结果相同。

(someClass?.StatusCode).ToString() == someClass?.StatusCode.ToString()

然而,他们没有:

(someClass?.StatusCode).ToString() 等于 string.Empty

并且someClass?.StatusCode.ToString()等于null

someClass?.StatusCode 的计算结果为 Nullable<HttpStatusCode>ToStringNullable 结果为空字符串。

someClass?.StatusCode.ToString() 将整个表达式短路为 null.

通过使用括号,您可以有效地分解整个表达式。

你的期望是错误的(这很明显),但让我解释一下原因..

someClass?.StatusCode.ToString()
.........^ here the evaluation is already concluded to null - someClass is null

(someClass?.StatusCode).ToString()
.......................^ null to string will result in an empty string

查看代码中进行计算的点 (^)。如果没有括号,评估将立即停止(它永远不会被 ToString 编辑)。使用括号进行评估,然后将出现 ToString。

正如 Daniel 所说,这是动态创建的类型为 Nullable 的结果。我应该考虑一下。

如前所述,不带括号的语句基本上会停止求值,因为 ? 右边的任何内容。如果什么在 ? 之前,(短路)变为空。一片空白。我了解运营商在这方面的工作方式。

带括号的语句强制创建 Nullable 类型的实例,因此调用 ToString 会产生空字符串。我没有想到,虽然它可能应该有。

我使用 LINQPad 获取 IL 并验证所有这些。我一开始就应该这样做。很抱歉浪费了大家的时间。不过,我非常感谢您的回答。谢谢。

class Program
{
    static void Main(string[] args)
    {
        SomeClass someClass1 = default(SomeClass);
        string result1 = someClass1?.SomeNumber.ToString();

        SomeClass someClass2 = default(SomeClass);
        string result2 = (someClass2?.SomeNumber).ToString();
    }
}

public class SomeClass
{
    public int SomeNumber { get; set; }
}



IL_0000:  nop
IL_0001:  ldnull
IL_0002:  stloc.0     // someClass1
IL_0003:  ldloc.0     // someClass1
IL_0004:  brtrue.s    IL_0009
IL_0006:  ldnull
IL_0007:  br.s        IL_0018
IL_0009:  ldloc.0     // someClass1
IL_000A:  call        UserQuery+SomeClass.get_SomeNumber
IL_000F:  stloc.s     04
IL_0011:  ldloca.s    04
IL_0013:  call        System.Int32.ToString
IL_0018:  stloc.1     // result1
IL_0019:  ldnull
IL_001A:  stloc.2     // someClass2
IL_001B:  ldloc.2     // someClass2
IL_001C:  brtrue.s    IL_002A
IL_001E:  ldloca.s    05
IL_0020:  initobj     System.Nullable<System.Int32>
IL_0026:  ldloc.s     05
IL_0028:  br.s        IL_0035
IL_002A:  ldloc.2     // someClass2
IL_002B:  call        UserQuery+SomeClass.get_SomeNumber
IL_0030:  newobj      System.Nullable<System.Int32>..ctor
IL_0035:  stloc.s     05
IL_0037:  ldloca.s    05
IL_0039:  constrained. System.Nullable<System.Int32>
IL_003F:  callvirt    System.Object.ToString
IL_0044:  stloc.3     // result2
IL_0045:  ret

SomeClass.get_SomeNumber:
IL_0000:  ldarg.0
IL_0001:  ldfld       UserQuery+SomeClass.<SomeNumber>k__BackingField
IL_0006:  ret

SomeClass.set_SomeNumber:
IL_0000:  ldarg.0
IL_0001:  ldarg.1
IL_0002:  stfld       UserQuery+SomeClass.<SomeNumber>k__BackingField
IL_0007:  ret