现在位置: 首页 > C# 教程 > 正文

C# 可空类型(Nullable)

在 C# 中,intfloatboolDateTime 等都是值类型,它们默认必须有一个值,不能为 null

int a = null;  // 编译错误:无法将 null 转换为 int

可空类型(Nullable Types) 解决了这个问题——它让值类型额外拥有一个"没有值"的状态(即 null),在处理数据库字段、API 返回值等可能缺失数据的场景中非常实用。

声明方式很简单,在类型后面加一个 ?

int? a = null;  // 合法
int? b = 42;    // 合法

这里的 int?Nullable<int> 的语法糖(简写形式),两者完全等价:

Nullable&lt;int&gt; a = null;   // 完整写法
int? a = null;              // 简写,效果相同

下图展示了可空类型在内存中的工作方式——它用一个额外的布尔标记来记录"是否有值":

Nullable<T> 的内存结构 int(普通值类型) Value: 42 4 bytes,始终有值 包装 Nullable<int>(可空类型) HasValue Value 1 byte 4 bytes 共 5+ bytes int? x = 42; true ✓ 42 HasValue = true,Value = 42 int? y = null; false ✗ HasValue = false,访问 Value 会抛异常

单问号 ? 与双问号 ?? 的区别

C# 中与可空类型相关的两个运算符经常一起使用,但含义完全不同:

运算符 名称 用途 示例
? 可空类型修饰符 让值类型可以为 null int? i = 3; 等价于 Nullable<int> i = new Nullable<int>(3);
?? 空合并运算符(Null-Coalescing Operator) 当变量为 null 时提供默认值 int result = i ?? 0;

一个简单的对比:

int i;     // 普通值类型,默认值为 0,永远不能是 null
int? ii;   // 可空类型,默认值为 null

可空类型的声明与赋值

声明语法

<data_type>? <variable_name> = null;

例如:

int? age = null;
double? temperature = 36.6;
bool? isActive = new bool?();   // 显式构造,默认为 null
DateTime? birthday = null;

Nullable<T> 可以表示其基础值类型的正常范围,再加上一个 null 值。例如 Nullable<int> 可以是 -2,147,483,6482,147,483,647 之间的任意整数,或者 null

完整示例

实例

using System;

namespace NullableDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 声明不同类型的可空变量
            int? num1 = null;
            int? num2 = 45;
            double? num3 = new double?();   // null
            double? num4 = 3.14157;
            bool? boolVal = new bool?();    // null

            // 显示值(null 会显示为空字符串)
            Console.WriteLine($"num1 = {num1 ?? 0}");         // 0
            Console.WriteLine($"num2 = {num2 ?? 0}");         // 45
            Console.WriteLine($"num3 = {num3 ?? 0.0}");       // 0
            Console.WriteLine($"num4 = {num4 ?? 0.0}");       // 3.14157
            Console.WriteLine($"boolVal = {boolVal}");         // (空)
        }
    }
}

输出结果:

num1 = 0
num2 = 45
num3 = 0
num4 = 3.14157
boolVal =

Null 合并运算符(??)

?? 运算符用于为可空类型或引用类型提供一个兜底默认值。当左侧不为 null 时返回左侧值,否则返回右侧值:

<表达式1> ?? <表达式2>
  • 如果 <表达式1> 不为 null,返回 <表达式1>
  • 否则返回 <表达式2>

从 C# 8.0 起,还可以使用 ??=(空合并赋值运算符),仅当变量为 null 时才赋值:

int? x = null;
x ??= 10;   // x 为 null,赋值为 10
x ??= 20;   // x 已有值 10,不再赋值

实例

using System;

namespace NullableDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            double? num1 = null;
            double? num2 = 3.14157;

            // ?? 提供默认值
            double result1 = num1 ?? 5.34;   // num1 为 null → 返回 5.34
            double result2 = num2 ?? 5.34;   // num2 有值   → 返回 3.14157

            Console.WriteLine($"num1 ?? 5.34 = {result1}");   // 5.34
            Console.WriteLine($"num2 ?? 5.34 = {result2}");   // 3.14157

            // 链式使用:提供多个备选值
            int? a = null;
            int? b = null;
            int? c = 42;
            int value = a ?? b ?? c ?? 0;    // 依次尝试,最终得到 42
            Console.WriteLine($"a ?? b ?? c ?? 0 = {value}"); // 42
        }
    }
}

输出结果:

num1 ?? 5.34 = 5.34
num2 ?? 5.34 = 3.14157
a ?? b ?? c ?? 0 = 42

可空类型的常用属性和方法

成员 说明 示例
.HasValue 判断变量是否有值(返回 bool if (num.HasValue) { ... }
.Value 获取实际值(若为 null 会抛 InvalidOperationException int x = num.Value;
.GetValueOrDefault() 安全获取值,若为 null 返回类型的默认值(如 0 num.GetValueOrDefault()
.GetValueOrDefault(T) 安全获取值,若为 null 返回指定的默认值 num.GetValueOrDefault(100)
?? 空合并运算符(语法糖,等价于 GetValueOrDefault int result = num ?? 100;

实例

using System;

namespace NullableDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int? num = null;

            // HasValue + Value(传统写法)
            if (num.HasValue)
                Console.WriteLine($"值为: {num.Value}");
            else
                Console.WriteLine("num 没有值");

            // 安全获取默认值
            Console.WriteLine(num.GetValueOrDefault());      // 0
            Console.WriteLine(num.GetValueOrDefault(99));    // 99
            Console.WriteLine(num ?? 99);                    // 99(等价写法)

            // &#x26a0; 注意:直接访问 .Value 会抛异常
            // int x = num.Value;  // InvalidOperationException!
        }
    }
}

实际应用场景

可空类型在实际开发中非常常见,尤其是处理可能缺失的数据时:

数据库字段映射

数据库中的字段允许为 NULL,用可空类型可以精确对应:

用户ID 年龄 是否激活
1 28 true
2 null false
int? age = GetUserAgeFromDB(userId: 2);

// 安全处理 null
string display = age.HasValue
    ? $"用户年龄:{age.Value}"
    : "年龄未知";

// 或者更简洁的写法
string display2 = $"用户年龄:{age ?? 0}";

可选参数与配置

// 配置项可能未设置
int? timeout = GetConfigValue("timeout");
int actualTimeout = timeout ?? 30;  // 未设置时使用默认值 30 秒

链式空值检查(C# 6.0+)

// 使用 ?. 运算符安全访问可能为 null 的对象
string? name = user?.Address?.City?.Name;
// 如果 user、Address、City 中任何一个为 null,结果就是 null,不会抛异常

小结

功能 示例 说明
定义可空类型 int? x = null; 等价于 Nullable<int>
判断是否有值 x.HasValue 返回 truefalse
获取值(不安全) x.Value 若为 null 会抛 InvalidOperationException
获取默认值 x ?? 0 若为 null 返回右侧值
获取指定默认值 x.GetValueOrDefault(10) 若为 null 返回 10
空合并赋值 x ??= 10 仅当 xnull 时赋值(C# 8.0+)
安全导航访问 user?.Name usernull 则返回 null(C# 6.0+)

C# 8.0 的"可空引用类型"

从 C# 8.0 开始,引入了 可空引用类型(Nullable Reference Types),它与本文介绍的可空值类型是两套不同的机制:

对比项 可空值类型 可空引用类型
示例 int? string?
作用对象 值类型(intboolstruct 等) 引用类型(stringclass、数组等)
实现方式 运行时通过 Nullable<T> 结构体实现 编译器静态分析,不改变运行时行为
默认状态 始终存在(从 C# 1.0 起) 需在项目中启用 <Nullable>enable</Nullable>
null 检查 .HasValue 判断 编译器发出警告(非错误)

可空引用类型是编译期的安全检查工具——它不会改变程序的运行时行为,但会在代码可能产生 NullReferenceException 的地方发出编译器警告,帮助你在开发阶段就发现潜在问题。