空条件运算符不适用于可空类型
本文关键字:类型 不适用 条件运算符 适用于 | 更新日期: 2023-09-27 18:06:12
我正在用c#6写一段代码,由于一些奇怪的原因,这段代码运行正常
var value = objectThatMayBeNull?.property;
但这不是:
int value = nullableInt?.Value;
通过不工作,我的意思是我得到一个编译错误说Cannot resolve symbol 'Value'
。知道为什么空条件运算符?.
不起作用吗?
好的,我做了一些思考和测试。结果如下:
int value = nullableInt?.Value;
编译时给出以下错误信息:
类型'int'不包含' Value'的定义
这意味着?
将int?
"转换"为实际的int
值。这实际上相当于:
int value = nullableInt ?? default(int);
结果是一个整数,显然没有Value
。
好的,这可能有帮助吗?
int value = nullableInt?;
不,这种语法是不允许的。
那又怎样?对于这种情况,请继续使用.GetValueOrDefault()
。
int value = nullableInt.GetValueOrDefault();
这样做的原因是使用null条件运算符访问值是没有意义的:
- 当您应用
x?.p
,其中p
是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
的属性,并且它以与参考类型PointClass
的非空属性相同的方式为结果添加可空性
关于可空类型,?.
操作符表示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
。?.
运算符在提取可空对象的内容方面也执行类似的操作。
对于您的特定示例,您应该使用??
操作符和适当的默认值,而不是?.
操作符。
我基本上同意其他的答案。我只是希望观察到的行为可以通过某种形式的权威文档来支持。
因为我在任何地方都找不到c# 6.0规范(它已经出来了吗?),我发现最接近"文档"的是2014年2月3日的c#语言设计说明。假设在那里找到的信息仍然反映了当前的状况,下面是正式解释观察到的行为的相关部分。
其语义类似于将三元操作符应用于空相等性检查、空文字和操作符的无问号应用,只是表达式只求值一次:
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[…])
其中
e0
与e
相同,除非e
为可空值类型,此时e0
为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;
正如其他人指出的那样,在您的情况下,您可能一直想使用空合并运算符??
,而不是空条件运算符?.
。
原因很简单(基于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.Value
是Cannot resolve symbol 'Value'
语法错误!
int
没有Value
属性
考虑:
var value = obj?.Property
等价于:
value = obj == null ? null : obj.Property;
这对int
没有意义,因此通过?.
对int?
也没有意义
旧的GetValueOrDefault()
对于int?
来说是有意义的。
或者,因为?
必须返回一些可空的东西,简单地:
int? value = nullableInt;
null条件运算符还对可为空的变量展开。因此,在"?."操作符之后," Value "属性就不再需要了。
我写了一篇文章,更详细地介绍了我是如何遇到这个问题的。如果你想知道 http://www.ninjacrab.com/2016/09/11/c-how-the-null-conditional-operator-works-with-nullable-types/