1. null合并操作符(??)
你要是不会写简洁又安全的nullable类型代码,那你对nullable类型的理解就还不完整。在C#里有两个超好用的操作符:null合并(??)和null条件操作符(?.)。
??操作符可以给可能是null的变量设置一个“默认值”。
想象一下,你有个用户名,可能是null。如果真的是null,你想显示“游客”。比如:
string userName = null;
string displayName = userName != null ? userName : "游客";
用??操作符可以写得更短:
string userName = null;
string displayName = userName ?? "游客";
怎么用:
- 如果左边不是null,就直接返回左边的值。
- 如果左边是null,就用右边的值。
再举几个例子:
int? age = null;
int displayAge = age ?? -1; // -1 —— 默认值
Console.WriteLine(displayAge); // 输出-1
string input = null;
string name = input ?? "游客";
Console.WriteLine($"你好, {name}!"); // 你好, 游客!
操作符链式用法
??可以连着用:
string result = str1 ?? str2 ?? "默认值";
如果str1不是null,就用它。否则用str2,如果它也是null,那就用"默认值"。
2. null条件操作符(?.)
还有一种常见情况——你只想在对象不是null的时候调用方法或者访问属性。比如:
User user = null;
string displayName = user != null ? user.Name : null;
用?.操作符可以更简单:
User user = null;
string displayName = user?.Name;
如果user对象是null,表达式不会报错,只会返回null。
举几个例子:
User user = null;
// 没有?. —— 会报错
// Console.WriteLine(user.Name); // NullReferenceException
Console.WriteLine(user?.Name); // 安全——会打印空字符串或者啥都不打印
Console.WriteLine(user?.GetProfileInfo()); // 也是一样
User[] users = null;
int? count = users?.Length; // 如果users == null,count就是null
操作符链式用法
你可以写一整条“链”:
string domain = company?.Director?.Email?.Split('@')?[1];
如果链路上任何一个是null,表达式就直接返回null,不会抛异常。
和??一起用
?.和??配合起来超好用:
string display = user?.Name ?? "未知";
如果user或者user.Name是null,就会输出“未知”。
3. !是啥,有啥用
抑制null警告的操作符
在新版C#(开了NRT模式)里,编译器会很贴心地提醒你:你在用一个可能是null的变量。但有时候你自己很确定没问题,这时候就可以用抑制操作符!。
string? possibleNull = GetUserNameMaybeNull();
Console.WriteLine(possibleNull.Length); // 警告:可能是null
Console.WriteLine(possibleNull!.Length); // 编译器不吭声,但如果真是null——直接异常
注意:!不会帮你防错——它只是告诉编译器“相信我”。如果变量真的是null,就会抛NullReferenceException。
啥时候别用它
别瞎用!。好习惯是尽量少用它。最好把代码写得null要么不可能出现,要么明确处理掉。
4. default——怎么拿到类型的“标准”值
有时候你就是想把变量“重置”到初始状态,尤其是不想手动写0、false或者null的时候。
这时候就用关键字default。
int a = default; // a == 0
bool flag = default; // flag == false
string s = default; // s == null
double? d = default; // d == null
在你的小程序里,比如可以重置名字或年龄:
userName = default; // null
userAge = default; // null(如果userAge是int?)
5. 区别和细节:?、!、default
就算是老司机,有时候看到?、!和default挨着也会懵。咱们来理理谁是谁。
?——你允许null
- 值类型:int? x —— 现在x可以是null。
- 引用类型:string? s —— 明确告诉编译器变量可以是null(NRT模式下)。
!——你保证不会有null
- user!.Name —— 你向编译器承诺user绝对不是null。
- 只在Nullable Reference Types模式下有用。
default——你要“默认值”
- int x = default; —— x会变成0
- string s = default; —— s会变成null
对比一下:
| 语法 | 这是什么? | 哪里能用? | 和null啥关系? |
|---|---|---|---|
|
Nullable值类型 | 到处都能用 | 可以赋值null |
|
Nullable引用类型(NRT) | NRT模式下 | 可以赋值null |
|
Null-forgiving操作符 | NRT模式下 | 抑制警告 |
|
默认值 | 到处都能用 | 引用类型就是null |
6. 用?、!和default时的常见坑
坑1:以为!能“治愈”null。
其实!只是让编译器别唠叨。如果值真的是null,程序还是会挂掉,抛NullReferenceException。
坑2:用.Value前忘了判断.HasValue。
尤其是int?、bool?这些nullable类型。不判断直接用可能会抛InvalidOperationException。
坑3:在需要“特殊”空值语义时用default。
比如0或false可能是合法值,不是“空”的意思。这样会导致逻辑bug。
坑4:在NRT模式下要么忘了给引用类型加?,要么乱加。
有人忘了加?,编译器就一堆警告。有人反过来,哪儿都加?,其实null根本不会出现。两种都让代码难读又不靠谱。
GO TO FULL VERSION