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

本文关键字:类型 不适用 条件运算符 适用于 | 更新日期: 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,其中pT类型的非空值时,结果为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[…])

其中e0e相同,除非e为可空值类型,此时e0e.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.ValueCannot 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/

相关文章: