C# 可空类型
`int?`、`Nullable<T>`、可空引用类型、以及 `?.`/`??` 组合。
Key Points值类型的 `?` 是 `Nullable<T>`;引用类型的 `?` 是编译器的空值检查标记。
值类型可空
C#
int? a = null;
int? b = 5;
Console.WriteLine(a.HasValue);
Console.WriteLine(b.HasValue);
Console.WriteLine(a.GetValueOrDefault());
Console.WriteLine(b.GetValueOrDefault(99));
Console.WriteLine(a ?? 0);
// 运算会传播 null
Console.WriteLine(a + 1 == null);
// 与普通 int 不能直接混算
// int c = a + 1; // 编译错误
int d = (a ?? 0) + 1;
Console.WriteLine(d);Output
False
True
0
5
0
True
1可空引用类型
C#
#nullable enable
string notNull = "一定不是 null";
string? maybeNull = null;
Console.WriteLine(notNull.Length);
Console.WriteLine(maybeNull?.Length);
// 编译器会警告:maybeNull 可能为 null
// Console.WriteLine(maybeNull.Length);
// 确定不为 null 时用 !
Console.WriteLine(maybeNull!.Length);Output
15string? and string are identical at runtime; the difference is compile time: string? means "may be null, check before use". It is on by default for new .NET 6+ projects.
Nullable reference types produce warnings, not errors. But the flagged spots are exactly where
NullReferenceException comes from in production. Enable Nullable and treat those warnings as errors.安全访问的三件套
C#
string? name = null;
// 1. ?. 短路
Console.WriteLine(name?.ToUpper() ?? "(空)");
// 2. ?? 提供默认值
Console.WriteLine(name ?? "匿名");
// 3. ?[] 索引
int[]? arr = null;
Console.WriteLine(arr?[0] ?? -1);
// 组合
var len = name?.Trim().Length ?? 0;
Console.WriteLine(len);Output
(空)
匿名
-1
0常见场景
C#
class User
{
public string? Nickname { get; set; }
public Address? Address { get; set; }
}
class Address
{
public string City { get; set; } = "";
}
var u = new User();
// 深层访问,任何一环为 null 都不会炸
var city = u.Address?.City ?? "未知";
Console.WriteLine(city);
// 想区分「没地址」和「城市为空」
if (u.Address is null)
{
Console.WriteLine("没有地址信息");
}
else
{
Console.WriteLine($"城市:{u.Address.City}");
}Output
未知
没有地址信息Use ?. when a default will do, is null when the cases differ. Do not mix them: if (u.Address?.City != null) conflates "no address" with "empty city".
可空与数据库
C#
// 数据库里的 NULL 列对应可空类型
class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal? Discount { get; set; } // 可能没有折扣
public DateTime? DeletedAt { get; set; } // 软删除
}
var p = new Product { Id = 1, Name = "键盘" };
var price = 199m;
var final = price * (1 - (p.Discount ?? 0));
Console.WriteLine(final);
Console.WriteLine(p.DeletedAt is null);Output
199
True值类型可空
C#
int? a = null;
int? b = 5;
Console.WriteLine(a.HasValue);
Console.WriteLine(b.HasValue);
Console.WriteLine(a.GetValueOrDefault());
Console.WriteLine(b.GetValueOrDefault(99));
Console.WriteLine(a ?? 0);
// 运算会传播 null
Console.WriteLine(a + 1 == null);
// 与普通 int 不能直接混算
// int c = a + 1; // 编译错误
int d = (a ?? 0) + 1;
Console.WriteLine(d);Output
False
True
0
5
0
True
1可空引用类型
C#
#nullable enable
string notNull = "一定不是 null";
string? maybeNull = null;
Console.WriteLine(notNull.Length);
Console.WriteLine(maybeNull?.Length);
// 编译器会警告:maybeNull 可能为 null
// Console.WriteLine(maybeNull.Length);
// 确定不为 null 时用 !
Console.WriteLine(maybeNull!.Length);Output
15string? 和 string 在运行时完全一样,区别只在编译期:string? 表示「可能是 null,用之前要检查」。这是 .NET 6+ 新项目默认开启的特性。
可空引用类型是警告不是错误。但它标出的位置往往就是线上
NullReferenceException 的来源。建议把 Nullable 设为 enable 并把相关警告当错误处理。安全访问的三件套
C#
string? name = null;
// 1. ?. 短路
Console.WriteLine(name?.ToUpper() ?? "(空)");
// 2. ?? 提供默认值
Console.WriteLine(name ?? "匿名");
// 3. ?[] 索引
int[]? arr = null;
Console.WriteLine(arr?[0] ?? -1);
// 组合
var len = name?.Trim().Length ?? 0;
Console.WriteLine(len);Output
(空)
匿名
-1
0常见场景
C#
class User
{
public string? Nickname { get; set; }
public Address? Address { get; set; }
}
class Address
{
public string City { get; set; } = "";
}
var u = new User();
// 深层访问,任何一环为 null 都不会炸
var city = u.Address?.City ?? "未知";
Console.WriteLine(city);
// 想区分「没地址」和「城市为空」
if (u.Address is null)
{
Console.WriteLine("没有地址信息");
}
else
{
Console.WriteLine($"城市:{u.Address.City}");
}Output
未知
没有地址信息?. 适合「拿个值,没有就给默认」,is null 判断适合「需要区分不同情况」。两者不要混用——if (u.Address?.City != null) 会把「没有地址」和「城市是空串」混为一谈。
可空与数据库
C#
// 数据库里的 NULL 列对应可空类型
class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal? Discount { get; set; } // 可能没有折扣
public DateTime? DeletedAt { get; set; } // 软删除
}
var p = new Product { Id = 1, Name = "键盘" };
var price = 199m;
var final = price * (1 - (p.Discount ?? 0));
Console.WriteLine(final);
Console.WriteLine(p.DeletedAt is null);Output
199
True