什么是 Nullable<t></t>
,它和 T?
是一回事吗
是的,
T?是
Nullable<t></t>的语法糖,仅适用于值类型(如
int、
DateTime、
bool)。引用类型(如
string、
List<int></int>)本身就能为
null,所以
string?在 C# 8+ 中表示“可为空引用类型”,和值类型的
int?语义不同,底层机制也完全不同。
关键区别在于:值类型加
?后,编译器生成的是
Nullable<int></int>结构体,它包含两个字段:
value(真实值)和
hasValue(是否已赋值),不是简单包装指针。
int? x = null;→ 实际是
new Nullable<int>()</int>,
hasValue == false
int? y = 5;→ 实际是
new Nullable<int>(5)</int>,
hasValue == true
typeof(int?) == typeof(Nullable<int>)</int>为
true
解包时用 .Value
还是 .GetValueOrDefault()
直接访问
.Value会在
hasValue == false时抛出
InvalidOperationException:“Nullable 对象必须具有一个值”。这在运行时才暴露,容易漏测。
.GetValueOrDefault()更安全:未赋值时返回
T的默认值(如
int?.GetValueOrDefault()返回
0),不抛异常。但要注意——它掩盖了“本应有值却缺失”的业务逻辑问题。 用
.Value:适合你**确定有值**的场景(比如刚做过
HasValue检查或来自可信输入) 用
.GetValueOrDefault():适合提供兜底值即可的场景(如数据库字段可能为空,前端显示“0”比崩溃好) 更推荐显式空合并:
int? x = ...; int actual = x ?? -1;,语义清晰且不可空
== null
和 HasValue == false
等价吗
等价,但编译后行为略有差异。C# 编译器对
== null做了特殊处理,会直接检查
hasValue字段,和手动写
!x.HasValue生成的 IL 几乎一致。
不过要注意:重载了
==运算符的自定义结构体,如果错误地把
Nullable<t></t>当普通类型处理,可能出错。标准值类型(
int、
Guid等)完全安全。
int? x = null; if (x == null) { ... } ✅ 安全、推荐
if (!x.HasValue) { ... } ✅ 语义更直白,尤其在复杂条件中
if (x.Equals(null)) { ... } ❌ 不推荐,调用虚方法,且 null传入可能触发装箱
数据库读取时 DbDataReader.GetFieldValue<t>()</t>
和 GetInt32()
的坑
ADO.NET 的
GetFieldValue<t>()</t>支持泛型,但对可空类型要求严格:若数据库列允许 NULL,且你传入
int?,它能正确返回;但若误传
int,遇到 NULL 会直接抛
InvalidCastException。
而传统方法如
GetInt32()、
GetString()全部不支持 NULL —— 遇到 NULL 就炸,必须先用
IsDBNull()判断。
var reader = cmd.ExecuteReader();
while (reader.Read())
{
// ✅ 安全:自动处理 NULL
int? age = reader.GetFieldValue<int?>(reader.GetOrdinal("age"));
<pre class='brush:php;toolbar:false;'>// ❌ 危险:NULL 时抛异常
// int age2 = reader.GetInt32(reader.GetOrdinal("age"));
// ✅ 传统方式(啰嗦但明确)
var dbVal = reader["age"];
int? age3 = dbVal == DBNull.Value ? null : (int?)dbVal;}
最易被忽略的一点:Entity Framework Core 的
FromSqlRaw或原生查询返回匿名类型时,若字段为 NULL,对应属性会是默认值(如
0),而不是
null—— 因为匿名类型属性推导基于非空类型。这时必须显式声明为可空,或改用元组/具体 DTO。
