C# 语言之可为null值类型

可为 null 值类型定义

可为 null 值类型 T? 表示其基础值类型 T 的所有值及额外的 null 值。例如,可以将以下三个值中的任意一个指定给 bool?变量: truefalsenull 。基础值类型 T 本身不能是可为空的值类型。

任何可为空的值类型都是泛型 System.Nullable<T> 结构的实例。可使用以下任何一种可互换形式引用具有基础类型 T 的可为空值类型: Nullable<T>T?

需要表示基础值类型的未定义值时,通常使用可为空的值类型。例如,布尔值或 bool 变量只能为 truefalse 。 但是,在某些应用程序中,变量值可能未定义或缺失。例如,某个数据库字段可能包含 truefalse,或者它可能不包含任何值,即 NULL 。在这种情况下,可以使用 bool? 类型。

声明和赋值

由于值类型可隐式转换为相应的可为空的值类型,因此可以像向其基础值类型赋值一样,向可为空值类型的变量赋值。还可分配 null 值。 例如:

  1. double? pi = 3.14;
  2. char? letter = 'a';
  3. int m2 = 10;
  4. int? m = m2;
  5. bool? flag = null;
  6. // An array of a nullable value type:
  7. int?[] arr = new int?[10];

可为空值类型的默认值表示 null ,也就是说,它是其 Nullable<T>.HasValue 属性返回 false 的实例。

检查可为空值类型的实例

C# 7.0 开始,可以将 is 运算符与类型模式 结合使用,既检查 null 的可为空值类型的实例,又检索基础类型的值:

  1. int? a = 42;
  2. if (a is int valueOfA)
  3. {
  4. Console.WriteLine($"a is {valueOfA}");
  5. }
  6. else
  7. {
  8. Console.WriteLine("a does not have a value");
  9. }
  10. // 输出:
  11. // a is 42

始终可以使用以下只读属性来检查和获取可为空值类型变量的值:

  • Nullable<T>.HasValue 指示可为空值类型的实例是否有基础类型的值。

  • 如果 HasValuetrue ,则 Nullable<T>.Value 获取基础类型的值。如果 HasValuefalse ,则 Value属性将引发 InvalidOperationException

以下示例中的使用 HasValue 属性在显示值之前测试变量是否包含该值:

  1. int? b = 10;
  2. if (b.HasValue)
  3. {
  4. Console.WriteLine($"b is {b.Value}");
  5. }
  6. else
  7. {
  8. Console.WriteLine("b does not have a value");
  9. }
  10. // 输出:
  11. // b is 10

还可将可为空的值类型的变量与 null 进行比较,而不是使用 HasValue 属性,如以下示例所示:

  1. int? c = 7;
  2. if (c != null)
  3. {
  4. Console.WriteLine($"c is {c.Value}");
  5. }
  6. else
  7. {
  8. Console.WriteLine("c does not have a value");
  9. }
  10. // 输出:
  11. // c is 7

从可为空的值类型转换为基础类型

如果要将可为空值类型的值分配给不可以为 null 的值类型变量,则可能需要指定要分配的替代 null 的值。使用 Null 合并操作符 ?? 执行此操作(也可将 Nullable<T>.GetValueOrDefault(T) 方法用于相同的目的):

  1. int? a = 28;
  2. int b = a ?? -1;
  3. Console.WriteLine($"b is {b}"); // 输出: b is 28
  4. int? c = null;
  5. int d = c ?? -1;
  6. Console.WriteLine($"d is {d}"); // 输出: d is -1

如果要使用基础值类型的默认值来替代 null ,请使用 Nullable<T>.GetValueOrDefault() 方法。

还可以将可为空的值类型显式强制转换为不可为 null 的类型,如以下示例所示:

  1. int? n = null;
  2. int n2 = (int)n; // 如果 n 为空则抛出异常

在运行时,如果可为空的值类型的值为 null ,则显式强制转换将抛出 InvalidOperationException

不可为 null 的值类型 T 可隐式转换为相应的可为空值类型 T?

提升的运算符

预定义的 一元运算符二元运算符值类型 T 支持的任何重载运算符也受相应的可为空值类型 T? 支持。如果一个或全部两个操作数为 null ,则这些运算符(也称为提升的运算符)将生成 null ,否则,运算符使用其操作数所包含的值来计算结果。 例如:

  1. int? a = 10;
  2. int? b = null;
  3. int? c = 10;
  4. a++; // a is 11
  5. a = a * c; // a is 110
  6. a = a + b; // a is null

注意:对于 bool? 类型,预定义的 &| 运算符不遵循此部分中描述的规则:即使其中一个操作数为 null ,运算符计算结果也可以不为 NULL

对于比较运算符 <><=>= ,如果一个或全部两个操作数都为 null ,则结果为 false 。否则,将比较操作数的包含值。请勿作出如下假定: 由于某个特定的比较(例如 <= )返回 false ,则相反的比较 ( > ) 返回 true

  • 既不大于等于 null ,

  • 也不小于 null

  1. int? a = 10;
  2. Console.WriteLine($"{a} >= null is {a >= null}");
  3. Console.WriteLine($"{a} < null is {a < null}");
  4. Console.WriteLine($"{a} == null is {a == null}");
  5. // 输出:
  6. // 10 >= null is False
  7. // 10 < null is False
  8. // 10 == null is False
  9. int? b = null;
  10. int? c = null;
  11. Console.WriteLine($"null >= null is {b >= c}");
  12. Console.WriteLine($"null == null is {b == c}");
  13. // 输出:
  14. // null >= null is False
  15. // null == null is True

对于相等运算符 == ,如果两个操作数都为 null ,则结果为 true ;如果只有一个操作数为 null ,则结果为false ;否则,将比较操作数的包含值。

对于不等运算符 != ,如果两个操作数都为 null ,则结果为 false ;如果只有一个操作数为 null ,则结果为true ;否则,将比较操作数的包含值。

如果在两个值类型之间存在用户定义的转换,则还可在相应的可为空值类型之间使用同一转换。

装箱和取消装箱

可为空值类型的实例 T? 已装箱,如下所示:

  • 如果 HasValue 返回 false ,则生成空引用。

  • 如果 HasValue 返回 true ,则基础值类型 T 的对应值将装箱,而不对 Nullable<T> 的实例进行装箱。

可将值类型 T 的已装箱值取消装箱到相应的可为空值类型 T? ,如以下示例所示:

  1. int a = 41;
  2. object aBoxed = a;
  3. int? aNullable = (int?)aBoxed;
  4. Console.WriteLine($"Value of aNullable: {aNullable}");
  5. object aNullableBoxed = aNullable;
  6. if (aNullableBoxed is int valueOfA)
  7. {
  8. Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
  9. }
  10. // 输出:
  11. // Value of aNullable: 41
  12. // aNullableBoxed is boxed int: 41

如何确定可为空的值类型

下面的示例演示了如何确定 System.Type 实例是否表示已构造的可为空值类型,即具有指定类型参数 TSystem.Nullable<T> 类型:

  1. Console.WriteLine($"int? is {(IsNullable(typeof(int?)) ? "nullable" : "non nullable")} value type");
  2. Console.WriteLine($"int is {(IsNullable(typeof(int)) ? "nullable" : "non-nullable")} value type");
  3. bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null;
  4. // 输出:
  5. // int? is nullable value type
  6. // int is non-nullable value type

如上面示例所示,使用 typeof 运算符来创建 System.Type 实例。

如果要确定实例是否是可为空的值类型,请不要使用 Object.GetType 方法获取要通过上面的代码测试的 Type 实例。 如果对值类型可为空的实例调用 Object.GetType 方法,该实例将装箱到 Object

由于对可为空的值类型的非 NULL 实例的装箱等同于对基础类型的值的装箱,因此 GetType 会返回表示可为空的值类型的基础类型的 Type 实例:

  1. int? a = 17;
  2. Type typeOfA = a.GetType();
  3. Console.WriteLine(typeOfA.FullName);
  4. // 输出:
  5. // System.Int32

另外,请勿使用 is 运算符来确定实例是否是可为空的值类型。如以下示例所示,无法使用 is 运算符区分可为空值类型实例的类型与其基础类型实例:

  1. int? a = 14;
  2. if (a is int)
  3. {
  4. Console.WriteLine("int? instance is compatible with int");
  5. }
  6. int b = 17;
  7. if (b is int?)
  8. {
  9. Console.WriteLine("int instance is compatible with int?");
  10. }
  11. // 输出:
  12. // int? instance is compatible with int
  13. // int instance is compatible with int?

可使用以下示例中提供的代码来确定实例是否是可为空的值类型:

  1. int? a = 14;
  2. Console.WriteLine(IsOfNullableType(a)); // output: True
  3. int b = 17;
  4. Console.WriteLine(IsOfNullableType(b)); // output: False
  5. bool IsOfNullableType<T>(T o)
  6. {
  7. var type = typeof(T);
  8. return Nullable.GetUnderlyingType(type) != null;
  9. }

注意:此部分中所述的方法不适用于可为空的引用类型的情况。