null 条件运算符不适用于可空类型?

null conditional operator not working with nullable types?

我正在用 c#6 编写一段代码,出于某种奇怪的原因,它可以工作

var value = objectThatMayBeNull?.property;

但这不是:

int value = nullableInt?.Value;

我说的不工作是指我收到一个编译错误 Cannot resolve symbol 'Value'。 知道为什么 null 条件运算符 ?. 不起作用吗?

int 没有 Value 属性.

考虑:

var value = obj?.Property

相当于:

value = obj == null ? null : obj.Property;

这对 int 没有意义,因此对 int? 没有意义,通过 ?.

虽然旧 GetValueOrDefault()int? 有意义。

或者就此而言,因为 ? 必须 return 一些可以为 null 的东西,简单地说:

int? value = nullableInt;

好的,我做了一些思考和测试。这是发生了什么:

int value = nullableInt?.Value;

编译时给出这个错误信息:

Type 'int' does not contain a definition for `Value'

表示?'converts'将int?转化为实际的int值。这实际上等同于:

int value = nullableInt ?? default(int);

结果是一个整数,显然没有 Value

好的,这有帮助吗?

int value = nullableInt?;

不,不允许使用该语法。

然后呢?对于这种情况,请继续使用 .GetValueOrDefault()

int value = nullableInt.GetValueOrDefault();

关于可空类型,?. 运算符表示 if not null, use the wrapped value。因此,对于可为空的 int,如果可为空的值为 8,则 ?. 的结果将为 8,而不是包含 8 的可为空的。由于 Value 不是 int 的 属性,您会得到一个错误。

因此,尝试使用 属性 Value 的示例完全正确地失败了,但以下内容会起作用,

var x = nullableInt?.ToString();

考虑空合并运算符,??

var x = nullableInt ?? 0;

在这里,操作员说,if null, return 0, otherwise return the value inside the nullable,在这种情况下是 int?. 运算符在提取可为 null 的内容方面执行类似。

对于您的具体示例,您应该使用 ?? 运算符和适当的默认值而不是 ?. 运算符。

原因是使用 null 条件运算符访问值是没有意义的:

  • 当您应用 x?.p 时,其中 p 是不可为 null 的值类型 T,结果的类型为 T?。同样的道理,nullableInt?.Value操作的结果必须可以为空。
  • 当您的 Nullable<T> 有一个值时,nullableInt?.Value 的结果将与该值本身相同
  • 当您的 Nullable<T> 没有值时,结果将是 null,这与值本身相同。

虽然使用 ?. 运算符访问 Value 没有意义,但访问可为空值类型的其他属性确实有意义。该运算符与可空值类型和引用类型一致地工作,因此这两个实现产生相同的行为:

class PointClass {
    public int X { get; }
    public int Y { get; }
    public PointClass(int x, int y) { X = x; Y = y; }
}
struct PointStruct {
    public int X { get; }
    public int Y { get; }
    public PointStruct(int x, int y) { X = x; Y = y; }
}
...
PointClass pc = ...
PointStruct? ps = ...
int? x = pc?.X;
int? y = ps?.Y;

在可空 struct 的情况下,运算符允许您访问基础类型 PointStruct 的 属性,并以与它相同的方式为结果添加可空性对于引用类型的不可为 null 的属性 PointClass.

我基本同意其他答案。我只是希望观察到的行为可以得到某种形式的权威文档的支持。

由于我在任何地方都找不到 C# 6.0 规范(已经出来了吗?),我发现最接近 "documentation" 的是 C# Language Design Notes for Feb 3, 2014。假设在那里找到的信息仍然反映了当前的事态,这里是正式解释观察到的行为的相关部分。

The semantics are like applying the ternary operator to a null equality check, a null literal and a non-question-marked application of the operator, except that the expression is evaluated only once:

e?.m(…)   =>   ((e == null) ? null : e0.m(…))
e?.x      =>   ((e == null) ? null : e0.x)
e?.$x     =>   ((e == null) ? null : e0.$x)
e?[…]     =>   ((e == null) ? null : e0[…])

Where e0 is the same as e, except if e is of a nullable value type, in which case e0 is e.Value.

将最后一条规则应用于:

nullableInt?.Value

...语义等价的表达式变为:

((nullableInt == null) ? null : nullableInt.Value.Value)

显然,nullableInt.Value.Value 无法编译,这就是您所观察到的。

至于为什么做出设计决定将该特殊规则具体应用于可空类型,我认为 dasblinkenlight 的回答很好地涵盖了这一点,所以我不会在这里重复。


此外,我应该提一下,即使假设我们没有针对可空类型的特殊规则,并且表达式 nullableInt?.Value 确实像您最初想象的那样编译和运行...

// let's pretend that it actually gets converted to this...
((nullableInt == null) ? null : nullableInt.Value)

仍然,您问题中的以下语句将无效并产生编译错误:

int value = nullableInt?.Value; // still would not compile

它仍然不起作用的原因是 nullableInt?.Value 表达式的类型是 int?,而不是 int。因此,您需要将 value 变量的类型更改为 int?.

C# Language Design Notes for Feb 3, 2014:

The type of the result depends on the type T of the right hand side of the underlying operator:

  • If T is (known to be) a reference type, the type of the expression is T
  • If T is (known to be) a non-nullable value type, the type of the expression is T?
  • If T is (known to be) a nullable value type, the type of the expression is T
  • Otherwise (i.e. if it is not known whether T is a reference or value type) the expression is a compile time error.

但是,如果您随后被迫编写以下内容以使其编译:

int? value = nullableInt?.Value;

...那么这似乎毫无意义,与简单地做没有什么不同:

int? value = nullableInt;

正如其他人所指出的,在您的情况下,您可能打算使用 null-coalescing operator ?? all along, not the null-conditional operator ?.

null 条件运算符还会解包可为 null 的变量。所以在“?”之后运算符,不再需要“值”属性。

我写了一篇 post,其中更详细地说明了我是如何遇到这个问题的。如果您想知道

http://www.ninjacrab.com/2016/09/11/c-how-the-null-conditional-operator-works-with-nullable-types/

仅仅是因为(根据上面sstan的回答)

var value = objectThatMayBeNull?.property;

由编译器计算,如

var value = (objectThatMayBeNull == null) ? null : objectThatMayBeNull.property

int value = nullableInt?.Value;

喜欢

int value = (nullableInt == null) ? null : nullableInt.Value.Value;

nullableInt.Value.ValueCannot resolve symbol 'Value'语法错误!