可空类型下的null条件运算符无法正常工作?

77

我正在使用C#6编写一段代码,出于某种奇怪的原因这个代码可以运行

var value = objectThatMayBeNull?.property;

但是这个不会:

int value = nullableInt?.Value;

当我说“不起作用”时,我的意思是我收到了一个编译错误,上面写着Cannot resolve symbol 'Value'。 你有什么想法,为什么空值条件运算符?.不能正常工作?


你试过这个吗:int value = (nullableInt?).Value; ??? - lem2802
7
在这种情况下,你应该使用??运算符,例如nullableInt ?? 0 - Jeff Yates
7个回答

38

好的,我进行了一些思考和测试。这就是发生的事情:

int value = nullableInt?.Value;

编译时出现以下错误信息:

类型“int”不包含名为“Value”的定义

这意味着?int?转换为实际的int值。这与以下代码的效果相同:

int value = nullableInt ?? default(int);

结果是一个整数,显然它没有“Value”。

好的,这可能有帮助吗?

int value = nullableInt?;

不,那种语法是不允许的。

那么怎么办呢?对于这种情况,只需继续使用 .GetValueOrDefault()

int value = nullableInt.GetValueOrDefault();

20
我本以为这会是这个运算符的主要用例之一,特别是因为它只是语法糖。 - linkerro

21
这是因为使用空条件运算符访问值是毫无意义的:
- 当你使用`x?.p`(其中p是非空值类型T)时,结果的类型是`T?`。同理,`nullableInt?.Value`操作的结果**必须**是可为空的。 - 当你的`Nullable`有值时,`nullableInt?.Value`的结果将与该值本身相同。 - 当你的`Nullable`没有值时,结果将为`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的属性,并以与引用类型PointClass的非可空属性相同的方式将nullability添加到结果中。


8
这里情况并非如此。这是因为,就像空值合并运算符一样,?. 解析为包装的 int 类型,而不是可空类型本身。 - Jeff Yates
1
既然你指出了这一点,我意识到我的逻辑是多么愚蠢。今天对科学来说不是一个好日子。 - linkerro
1
它们无法编译,因为当可空类型不为空时,?. 的结果是其中的实际 int,它没有 ValueHasValue 属性。就像我的答案中所述,尝试使用 nullableInt?.ToString() - Jeff Yates
5
@dasblinkenlight DateTime? dt=DateTime.Now; int? day=dt?.Day;. 在?.之后应该是DateTime的属性而不是DateTime?的属性。 - user4003407
因为 ?. 可能返回 null,这是错误的。实际上它会返回一个 int。所以 someInt?.XXX 的最终结果与 someInt.GetValueOrDefault().XXX 相同。 - Patrick Hofman
@PatrickHofman:不,应该这么写: someInt.HasValue ? someInt.GetValueOrDefault().XXX : null - Ben Voigt

11
关于可空类型,?. 操作符的意思是 如果不为空,则使用包装的值。因此对于可空的 int 类型,如果可空类型有值 8,则 ?. 的结果将是 8,而不是包含 8 的可空类型。由于 Value 不是 int 的属性,因此会出现错误。
因此,尝试使用属性 Value 的示例完全失败,但以下内容可以正常工作: var x = nullableInt?.ToString(); 考虑到空合并运算符 ??var x = nullableInt ?? 0; 在这里,操作符表示 如果为空,则返回 0,否则返回可空类型内部的值,在这种情况下,它是一个 int。与提取可空类型的内容类似,?. 操作符执行得也相似。
对于您的特定示例,应该使用 ?? 运算符和适当的默认值,而不是 ?. 运算符。

var x = nullableInt ?? 0 无法编译,因为 nullableInt0 是不同的类型:分别是 Nullable<int>int。你必须使用 nullableInt.GetValueOrDefault(0) 或者 nullableInt.GetValueOrDefault(),因为 default(int) 就是 0 - Suncat2000
1
@Suncat2000,那绝对不是真的。https://dotnetfiddle.net/NDPJ44 - Jeff Yates

7
我基本上同意其他回答。我只是希望能够通过某种权威文档支持所观察到的行为。
由于我找不到C# 6.0规范(它已经发布了吗?),我找到的最接近“文档”的是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?

这也在2014年2月3日C#语言设计注释中正式涵盖:

结果类型取决于底层操作符右侧的类型T: - 如果T是已知的引用类型,则表达式的类型为T。 - 如果T是已知的非空值类型,则表达式的类型为T?。 - 如果T是已知的可空值类型,则表达式的类型为T。 - 否则(即不知道T是引用类型还是值类型),表达式是编译时错误。
但如果你被迫写下面的代码以使其编译:
int? value = nullableInt?.Value;

如果是这样,那么似乎毫无意义,与仅仅执行以下操作没有任何区别:

int? value = nullableInt;

正如其他人所指出的,在您的情况下,您可能本来就是想使用 null-coalescing运算符??,而不是null-conditional运算符?.


啊!抓住你了!实际上我找到了这个线程,因为在执行“long? nullableLongVariable = objectVariable.NullableLongProperty.Value;”时出现了对象引用错误,我犯蠢了,把它改成了“long? nullableLongVariable = objectVariable.NullableLongProperty?.Value;”,而我本可以直接使用“long? nullableLongVariable = objectVariable.NullableLongProperty;”。 - Tom

1
仅仅因为(基于上面sstan的回答)。
var value = objectThatMayBeNull?.property;

被编译器评估的方式类似于HTML中的

标签。

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

int value = nullableInt?.Value;

喜欢

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

nullableInt.Value.Value出现Cannot resolve symbol 'Value'语法错误时!

0

0

int 没有 Value 属性。

请考虑:

var value = obj?.Property

等同于:

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

这在使用 int 时没有意义,因此也不适用于 int?,可以使用 ?.

旧的 GetValueOrDefault() 在使用 int? 时确实有意义。

或者说,由于 ? 必须返回可空类型,因此可以简单地使用:

int? value = nullableInt;

1
这不是一个整数,它是一个整数吗? - lem2802

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接