十九、可空类型(Nullable)

零、为什么引入可控类型?

原因 描述
现实需要 业务场景中常有“无值”的可能
类型安全 让值类型支持 null,增强类型系统
框架友好 更好地支持 ORM/序列化/LINQ 等
表意清晰 避免使用特殊默认值,如 -1 或 0
编译检查 编译器能提示未检查 null 的逻辑

一、什么是可空值类型?

int? a = null; // 相当于 Nullable<int> a = new Nullable<int>();
  • int?Nullable<int> 的语法糖。

  • 可空类型只有两种状态:

    • HasValue = false → 表示为 null

    • HasValue = true → 表示有实际值


二、空接合操作符(??)

int? x = null;
int y = x ?? 10; // 若 x 为 null,y 将被赋值为 10

这是 C# 的语法糖,目的是简化 null 检查和默认值设定逻辑。


三、CLR 对 Nullable 的特殊支持

CLR 在处理可空值类型时做了许多优化,确保它们运行时与普通结构体不同:

1️⃣ 装箱行为

int? x = 5;
object o = x; // 只会装箱 int,不会装箱整个 Nullable<int>
  • 如果 HasValue = true,只装箱值。

  • 如果 HasValue = false,则 o 被赋为 null

2️⃣ 拆箱行为

object o = 5;
int? x = (int?)o; // 正常
  • 如果 o == null,结果为 int? = null

  • 如果 o 是值,CLR 会自动包装成 Nullable<T>


四、通过 Nullable 调用方法的行为

int? x = 5;
Console.WriteLine(x.GetType()); // 输出:System.Int32

说明GetType() 是调用的真实值类型的方法,而不是 Nullable 本身。


五、使用建议

使用场景 推荐方式
数据库字段对接 int?, DateTime?
与 UI 控件绑定 判断 HasValueValue
避免装箱开销 优先使用原始值或 default

六、示例代码

public static string FormatAge(int? age)
{
    return $"年龄:{age?.ToString() ?? "未填写"}";
}
public void Save(int? score)
{
    object boxed = score;
    Console.WriteLine(boxed ?? "空值");
}

七、可能的坑点 ⚠️

object o = null;
int? i = (int?)o; // ✔️ 正确,返回 null

int i2 = (int)o; // ❌ NullReferenceException

八、面试题推荐:

1. C# 中值类型能否赋值 null?怎么实现?

默认情况下,值类型(如 int、double、struct)不能赋值为 null,因为它们在内存中直接存储值。

但从 C# 2.0 开始,可以使用 Nullable<T> 或简写形式 T? 来创建可空值类型,从而支持 null:

int? age = null;             // 等价于 Nullable<int> age = new Nullable<int>();
Nullable<double> score = 95; // 明确使用 Nullable<T>

2. Nullable<int>int? 有什么区别?

它们在 语义和运行时表现上完全一致

  • int?Nullable<int> 的语法糖(语言层面的简写)

  • 编译后生成的 IL 和 CLR 识别都是 System.Nullable<T>

推荐使用 int?:更简洁、清晰,也符合 C# 风格规范。


3. 装箱 Nullable<T> 时行为如何?与普通值类型有何不同?

普通值类型装箱

int x = 5;
object o = x; // 生成新的对象,装箱了 int

可空值类型装箱

int? x = 5;
object o = x; // 实际上只装箱 int,不是整个 Nullable<int>

int? y = null;
object o2 = y; // o2 被赋值为 null,而不是 box 任何值

结论

  • HasValue = true:只装箱内部值

  • HasValue = false:结果就是 null(避免无意义装箱)


4. 如何优雅地给 int? 提供默认值?

使用 空接合操作符 ??,简洁高效:

int? age = null;
int realAge = age ?? 18; // 如果 age 是 null,就用默认值 18

还可以配合表达式简写:

Console.WriteLine($"年龄:{age?.ToString() ?? "未填写"}");

5. Nullable<T> 能否作为 Dictionary 的 Key?注意事项?

可以,但有几点注意:

✅ 可作为 Key 的前提:

  • Nullable<T> 是值类型,可以参与字典键比较

  • Dictionary 使用 EqualsGetHashCode 来判断键的唯一性

⚠️ 注意事项:

  • null 本身不能作为 Dictionary 的 Key

  • 使用 int? 作为 Key 时,null5 是不同键:

var dict = new Dictionary<int?, string>();
dict[null] = "空值";
dict[5] = "五";

Console.WriteLine(dict[null]); // 输出:空值
Console.WriteLine(dict[5]);    // 输出:五

建议:在使用可空类型作为键时,明确区分是否允许 null 作为 Key,或使用 TryGetValue 安全访问。

posted @ 2025-08-26 10:06  世纪末の魔术师  阅读(14)  评论(0)    收藏  举报