在 C# 中,类型定义中的**问号(?
)**主要用于控制类型的可空性,但具体行为因类型(值类型或引用类型)和 C# 版本而异。以下是清晰分类的说明:
一、可空值类型(T?
,适用于所有 C# 版本)
用途 :允许值类型(如 int
、DateTime
等)存储 null
值。
语法 :在值类型后加 ?
,底层由 System.Nullable<T>
结构实现。
示例:
int? age = null; // 声明可空整型 DateTime? date = null; // 声明可空日期
核心操作:
-
判空 :通过
HasValue
属性检查是否有值。if (age.HasValue) Console.WriteLine(age.Value);
-
安全取值 :使用
??
提供默认值,避免InvalidOperationException
。int safeAge = age ?? 0; // 若 age 为 null,返回 0
-
强制转换 :直接将
int?
赋值给int
会报错,需显式转换。int value = (int)age; // 若 age 为 null,抛出异常
适用场景:
- 数据库字段可能为
null
(如int?
对应 SQL 中的NULL
整数字段)。 - 需要区分"未赋值"和"有效值"(例如
0
和null
语义不同)。
二、可空引用类型(T?
,C# 8.0+)
用途 :在严格模式下显式标记引用类型可为 null
,避免空引用异常。
语法 :在引用类型后加 ?
(需启用 #nullable enable
)。
示例:
#nullable enable string? name = null; // 显式声明可为 null 的字符串 string title = null; // 严格模式下会警告:需改为 string?
核心规则:
-
严格模式 :启用后,引用类型默认不可为
null
,需显式用?
标记。#nullable enable public string? GetComment() { ... } // 可能返回 null
-
安全访问 :使用
?.
和??
避免运行时异常。int length = name?.Length ?? 0; // 安全访问属性
适用场景:
-
API 设计:明确参数或返回值是否可为
null
。public void SaveData(string id, string? optionalNote = null) { ... }
-
反序列化 JSON 数据:处理可能缺失的字段。
public class User { public string Name { get; set; } // 必须存在 public string? Email { get; set; } // 允许为 null }
三、关键区别与注意事项
特性 | 可空值类型(int? ) |
可空引用类型(string? ) |
---|---|---|
适用类型 | 值类型(struct ) |
引用类型(class ) |
底层实现 | System.Nullable<T> 结构 |
编译时静态分析,无运行时类型变化 |
默认可空性 | 必须用 ? 声明才可为 null |
严格模式下默认不可为 null |
运行时表现 | 实际存储为 T 或 null |
编译警告,但运行时仍可能为 null |
四、常见问题与解决
-
**错误:不可空类型接收
null
**string name = null; // 严格模式下警告
修复:
string? name = null; // 显式标记可空
-
错误:未处理可空值类型
int? age = null; int value = age; // 编译错误
修复:
int value = age ?? 0; // 提供默认值
-
安全调用链
var length = user.Address?.City?.Length; // 避免多层 null 检查
五、最佳实践
- 启用严格模式 :在
.csproj
中配置<Nullable>enable</Nullable>
,提升代码安全性。 - 明确可空性 :公共 API 的参数和返回值显式标记
?
。 - 防御性编程 :对可空类型进行判空(
if (x != null)
)或使用??
、?.
运算符。
通过合理使用 ?
,可以显著减少空引用异常,提升代码健壮性,尤其在处理外部数据(如数据库、API 响应)时至关重要。