撰写了文章 发布于 2020-04-20 16:15:50
从C#开始的编程入门——可空类型
如果我问你C#中有哪些类型的值可以为null,那么你自然会答道所有引用类型的变量可以为null。
确实,我们从另一个方面来思考这个问题。引用类型可以为null,但是值类型不能为null。引用类型如果不被初始化那么就是null,而值类型不手动初始化那么就会默认初始化各个成员为默认值。
这里又引出了另外一个问题。我们提到过,构造引用类型对象时,其中的值类型成员也会被分配到堆中。那么我们还没有思考过的问题是,一个引用类型的成员在构造值类型对象时会发生什么?
其实我们已经见过包含引用类型成员的值类型了,表示元组的ValueTuple就是一个结构,但它的成员可以是引用类型的。那么复制一个包含引用类型成员的值类型对象,引用类型成员所引用的对象也会发生复制吗?
之所以前面没详细讲这个问题,是因为值类型中的引用类型成员确实没什么特殊的。首先答案是不会。只会复制引用本身。C#为了方便开发者,刻意地在语法上不对引用和值进行区分,虽然方便但却隐藏了一些细节可能让人捉摸不透(这样看来C/C++的指针反而更容易理解)。引用类型的变量只代表引用,你可以把引用也当成一种“值”来看,它记录了对象在堆中的位置。因此值类型复制的时候,也只会复制这个引用的“值”,而不是引用所保存的值对应在堆中的某个位置上的对象(也就相当于只复制了指向对象的指针)。
因此对于值类型中的引用类型成员,默认值就是null。不会假定一个引用指向任何对象。
现在问题来了。引用类型的null值代表什么都没有,这个对象也不可用。而值类型却没有这样的一个“空”值。比如int被初始化为0,但是并非所有情况下0都代表什么都没有或者没有意义。比如你要记录温度,0摄氏度并不是代表“没有温度”,它只是代表一个相对位置。如果你只是今天还没记录温度,而温度在冬天可能为0度,那么末日初始化为0可能会造成困扰。
那么究竟能不能让一个值类型真正地为“空”呢?
答案是可以的。这就是可空值类型(nullable value type,微软语言门户中的建议是把nullable type翻译成可为null的类型,但是这也太拗口了,请允许我叫它可空类型)。
可为null的值类型
从C#2.0开始,引入了一种特别的类型叫做可为null的值类型。它的基础类型是一个范型类型Nullable。他代表值类型T的所有可能的取值以及null。这样一来即使是值类型也可以取null值表示没有值。
C#提供了语法糖来构造这样的可空类型。
int? temperature = null;
在声明值类型变量时,在类型名称后面加上?就代表这是一个Nullable类型的可空值类型变量。我们可以用null来初始化它。
除此之外,可空类型还提供了一些成员来操作这样的类型。HasValue属性代表这个变量现在是否有可用的值(是否为null)。
赋值运算符经过了重载,可以直接使用对应的一般值类型来初始化可空值类型。
if (!temperature.HasValue)
{
temperature = 25;
}
但是反过来则不行。由于可空值类型可能为null,因此在尝试通过可空值类型为一般值类型赋值事依然需要通过安全的方式进行。Value属性代表可空类型的值,可以通过这个值是否为null来判断是否进行赋值。
if (temperature.HasValue)
{
int n = temperature.Value;
}
这里更加简洁的写法是使用null合并运算符。如果不为null就会获取Value属性的值,否则为右边的值。
int t = temperature ?? 0;
要注意的是,Nullable也是一个值类型而不是引用类型。因此只有引用类型可以为null的结论也就是错误的了。
Nullable的类型参数只能为值类型,而引用类型本身就可以为null,因此这条约束也就是自然而然的了。
可空值类型让值类型也可以为null,那么有没有办法让引用类型不能为null呢?这么犯贱的要求,实际上也得到了满足。
可空引用类型
实际上这个要求并非犯贱。NullReferenceException可能是很多新手程序员一开始会遇到的几大异常之一。就是因为操作引用类型时在各种情况下都有可能尝试在null**问成员。对于有些场景来说,这可能是非常严重的错误,原本不应该为null的东西突然变成了null(比如你**开着开着发现车没了),对于引用类型是否可以为null加以约束,在某些情况是有必要的。
注意:下面的功能只支持C#8.0以上的版本。这里只做简单介绍
可空引用类型是C#8.0起开始可用的功能,目前默认关闭需要针对每个项目手动开启,如果你想要尝试体验这个功能,我建议你新建一个项目。
首先需要编辑项目文件(*.csproj),如果你使用的是一般的编辑器那么把它直接当文本文件打开就可以编辑了。Visual Studio系的IDE应该是需要在右键菜单中手动选择编辑项目文件。
<Nullable>enable</Nullable>
在其中添加下面的内容表示启用可空引用类型的特性。
然后在源文件中添加一行指令:
#nullable enable
这实际上是为了满足某些要求严格的情况,编译器会在编译时检查出一些潜在的问题并做出警告。现在所有的类类型都默认不能为null。如果你尝试把null给一个类类型的变量,会发出警告。
string s = null; // warning CS8600: Converting null literal or possible null value to non-nullable type.
s.Contains(“a”); // Dereference of a possibly null reference. [Whatever]csharp(CS8602)
当我们把null赋值给s时,编译器发出警告,把null字面量或可能为null的值交给了不能为null的类型。
随后我们尝试在这个为null的对象上调用方法,编译器警告这是在解引用(dereference,reference的反义词。获取引用所指向的对象,也译作反引用)可能为null的引用。
开启这样的特性之后,要容忍引用类型的变量可以为null,就需要使用到和可空值类型一样的语法来表明可空,也就是在类型名称后面加上问好。但是要注意的是,实际上可空引用类型并不会使用到其它类型也就是说实际上Class和Class?都是用同一个类型来表达的。
string? s = null;
在某些时候我们可能能够确定某个变量不为null,但如果它是可空引用类型则仍然会发生警告,这个时候可以使用到null包容运算符(null-forgiving operator)来阻止产生警告。
Console.WriteLine(s!.Length);
使用这样的约束可以预防一些在运行时访问null的情况。但是目前来说C#中的这项特性主要是在编译时进行分析并发出警告,实际上没有太强的强制性。如果你了解过苹果的Swift,你就会发现空引用类型实际上有点类似于其中的Optional类型,Swift代码中经常出现密密麻麻的问好和感叹号,并且一不小心就不让你通过编译。
目录
陆秋之 1年前
黛黛冬优子 [作者] 1年前
陆秋之 1年前
Eliton 1年前
黛黛冬优子 [作者] 1年前
发布
Ellsworth 1年前
谢谢有被帮到
发布
Sketch 1年前
发布