Null条件运算符评估bool不bool? 正如所料

我刚刚从VS 2010升级到2015.我喜欢新的零条件运算符 ,也称为零传播。 这样可以简化代码,例如:

string firstCustomerName = customers?[0].Name; // null if customers or the first customer is null 

另一个:

 int? count = customers?[0]?.Orders?.Count(); // null if customers, the first customer, or Orders is null 

即使Enumerable.Count返回一个int来区分有效计数和之前的任何nulls ,它返回Nullable 。 这非常直观且非常有用。

但是为什么这会按预期编译和工作(它返回false ):

 string text = null; bool contains = text?.IndexOf("Foo", StringComparison.CurrentCultureIgnoreCase) >= 0; 

它应该返回bool? (它没有)或不编译。

你实际拥有的是什么

 string text = null; int? index = text?.IndexOf("Foo", StringComparison.CurrentCultureIgnoreCase); bool contains = index >= 0; 

int? >= int int? >= int是完全合法的。

它被拆分的原因是运算符的文档说明“如果条件成员访问和索引操作链中的一个操作返回null,则链的其余部分执行停止。表达式中优先级较低的其他操作继续。” 那意味着.? 只会在“创建一个值”之前评估具有相同优先级或更高优先级的事物。

如果查看运算符优先级的顺序,您将看到列表中的“关系和类型测试运算符”要低得多,因此将在应用>=之前创建该值。


更新:因为它是在注释中提出的,所以这里是关于>=和其他运算符在处理可空值时如何表现的C#5规范部分。 我找不到C#6的文档。

7.3.7提升运营商

提升的运算符允许在非可空值类型上运行的预定义和用户定义的运算符也可以与这些类型的可空forms一起使用。 提升运算符由满足特定要求的预定义和用户定义的运算符构成,如下所述:

  • 对于一元运算符
    + ++ - -- ! ~

    如果操作数和结果类型都是非可空值类型,则存在提升forms的运算符。 提升forms是通过添加一个? 操作数和结果类型的修饰符。 如果操作数为null,则提升的运算符将生成空值。 否则,提升的运算符解包操作数,应用基础运算符,并包装结果。

  • 对于二元运算符
    + - * / % & | ^ << >>

    如果操作数和结果类型都是非可空值类型,则存在提升forms的运算符。 提升forms是通过添加一个? 每个操作数和结果类型的修饰符。 如果一个或两个操作数为空,则提升的运算符产生空值(例外是bool?类型的&和|运算符,如第7.11.3节中所述)。 否则,提升的运算符解包操作数,应用基础运算符,并包装结果。

  • 对于等于运算符
    == !=

    如果操作数类型都是非可空值类型并且结果类型是bool,则存在提升forms的运算符。 提升forms是通过添加一个? 每个操作数类型的修饰符。 提升的运算符认为两个空值相等,并且空值不等于任何非空值。 如果两个操作数都为非null,则提升的运算符将解包操作数并应用基础运算符以生成bool结果。

  • 对于关系运算符
    < > <= >=

    如果操作数类型都是非可空值类型并且结果类型是bool,则存在提升forms的运算符。 提升forms是通过添加一个? 每个操作数类型的修饰符。 如果一个或两个操作数为空,则提升的运算符将生成值false。 否则,提升的运算符解包操作数并应用基础运算符以产生bool结果。

如果您使用此代码并将鼠标hover在x ,您会看到x是一个int?

 var x = text?.IndexOf("Foo", StringComparison.CurrentCultureIgnoreCase); bool contains = x >= 0; 

因此打字仍然是正确的。

那么让我们看看x >= 0 :那是一个int? >= int int? >= int 。 在可空和非可空结构之间有一个运算符。 这就是为什么它有效。

如果查看IL,您会看到它实际上调用了HasValueGetValueOrDefault() 。 我猜有一个操作符这样做,但我在参考源中找不到它,所以它应该在CLR或编译器中:

 instance !0 valuetype [mscorlib]System.Nullable`1::GetValueOrDefault() ... instance bool valuetype [mscorlib]System.Nullable`1::get_HasValue()