一、概述
null
关键字是表示不引用任何对象的空引用的文字值。null
是引用类型变量的默认值。普通值类型不能为null
,可为空的值类型除外
二、可为空的值类型
可为null值类型T?表示其基础值类型T的所有值及额外的null值。基础值类型T本身不能是可为空的值类型。
任何可为空的值类型都是泛型System.Nullable<T>结构的实例。可使用以下任何一种互换形式引用具有基础类型T的可为空值类型:Nullable<T>或T?。
需要表示基础类型的未定义值时,通常使用可为空的值类型。如:布尔值或bool变量只能为true或false。但是,在某些应用程序中,变量值可能未定义或缺失。在这种情况下可以使用bool?类型。
2.1 声明和赋值
由于值类型可隐式转换为相应的可为空的值类型,因此可以像其基础类型赋值一样,向可为空值类型的变量赋值,还可以分配null
值。具体实例如下:
double? pi=3.1415926; char? letter='c'; int a=110; int? ab=a; bool? flag =null; int?[] arr= new int?[8];
可为空值类型的默认值表示null
,也就是说,它是其Nullable<T>.HasValue
属性返回false
的实例。
2.2 检查可为空值类型
可将is运算符与类型模式结合使用,既检查null
的可为空值类型的实例,又检索基础类型的值:
int? a = 42; if(a is int valueOfA){ Console.WriteLine($"a is {valueOfA}"); }else{ Console.WriteLine("a does not have a value."); }
始终可以使用以下只读属性来检查和获取可为空值类型变量的值:
- Nullable<T>.HasValue指示可为空值类型的实例是否有基础类型的值。
- 如果HasValue为true,则Nullable<T>.Value获取基础类型的值。如果HasValue为false,则Value属性将引发IvalidOperationException。
使用HasValue
属性在显示值之前测试变量是否包含该值:
int? b=10; if(b.HasValue) { Console.WriteLine($"b is {b.Value}"); }else{ Console.WirteLine("b does not have a value."); }
还可以通过可为空类型与null进行比较,如:
int? bc = 7; if (bc != null) { Console.WriteLine($"c is {c.Value}"); } else { Console.WriteLine("c does not have a value"); }
2.3 基础类型与可为空的值类型互换
如果要将可为空值类型的值分配给不可以为null
的值类型变量,则可能需要指定要分配的替代null
的值。使用Null
和并操作符??
执行此操作(也可将Nullable<T>.GetValueOrDefault(T)
方法用于相同的目的):
int? pi=3.14; int b = pi??-1; Console.WriteLine($"b is {b}"); int? c=null; int d =c??-1; Console.WriteLine($"d is {d}");
如果要使用基础值类型的默认值为null
,需要使用Nullable<T>.GetValueOrDefault()
方法。
还可以将可为空的值类型显示强制转换为不可为null
的类型。如下:
int? n = null; int n2 = (int)n;
在运行时,如果可为空的值类型的值为 null
,则显式强制转换将抛出InvalidOperationException
。不可为null的值类型T
隐式转换为相应的可为空值类型T?
。
2.4 可为空的值类型装箱和取消装箱
可为空值类型的实例T?已装箱;
- 如果
HasValue
返回false
,则生成空引用。 - 如果
HasValue
返回true
,则基础值类型T的对应值将将装箱,而不对Nullable<T>
的实例进行装箱。
可将值类型T
的已装箱值取消装箱到相应的可为空值类型T?
,如示例:
2.5 如何确定可为空的值类型
如果要确定实例是否是可为空的值类型,请不要使用Object.GetType方法获取要通过前面的代码测试的Type实例。如果对值类型可为空的实例调用Object.GetType方法,该实例将装箱到Object。由于对可为空的值类型的非NULL实例的装箱等同于对基础类型的值的装箱,因此GetType会返回表示可为空的值类型的基础类型的Type实例。
另外,请勿使用is运算符来确定实例是否是可为空的值类型。因为无法使用is运算符区分可为空值类型实例的类型与其基础类型实例。
综合来说,如果要判断可为空值类型,需要使用Nullable.GetUnderlyingType和typeof运算符。以检查实例是否具有可为空的值类型。
三、可为 null 的引用类型
由于可为null的感知上下文选择加入代码,可以使用可为null的引用类型。可为null的引用类型,null静态分析警告和null包容运算符是可选的语言功能。
在可为null的感知上下文中:
- 引用类型T的变量必须用非null值进行初始化,并且不能为其分配可能为null的值。
- 引用类型T?的变量可以用null进行初始化,也可以分配null,但是在取消引用之前必须对null进行验证检测。
- 类型为T?的变量m在应用null包含运算符时被认为是非空的。
不可为null的引用类型T和可为null的引用类型T?之间的区别按照编译器对上述规则的解释强制执行的,类型为T的变量和类型为T?的变量由相同的.NET类型表示。
string notNull = "Hello"; string? nullable = default; notNull = nullable!; Console.WriteLine(notNull);
变量notNull和nullable都由String
类型表示。因为不可为null
的类型和可为null的类型都存储为相同的类型,所以有几个位置不允许使用可为null的引用类型。
下面几种情况不能使用可为nulll类型:
- 可为 null 的引用类型不能用作基类或实现的接口
- 可为 null 的引用类型不能用于任何对象创建或类型测试表达式
- 可为 null 的引用类型不能是成员访问表达式的类型
public MyClass : System.Object? // not allowed { } var nullEmpty = System.String?.Empty; // Not allowed var maybeObject = new object?(); // Not allowed try { if (thing is string? nullableString) // not allowed Console.WriteLine(nullableString); } catch (Exception? e) // Not Allowed { Console.WriteLine("error"); }