1. NullReferenceException的问题
基本上每个刚学C#的(甚至老手)程序员都遇到过这个吓人的提示:
System.NullReferenceException: 对象引用未设置为对象的实例。
来,经典场景:
string hello = null;
Console.WriteLine(hello.Length); // 砰! NullReferenceException
意思很简单:你想访问一个根本不存在的对象。也就是说变量指向了null——就是啥都没有。
为啥这种错误容易犯?
因为C#里大多数引用类型历史上都能赋值为null。程序员经常忘了检查变量到底是不是指向了对象,结果就中招,遇到著名的NullReferenceException。
如果每遇到一次NullReferenceException都能拿一美元,估计早就写出自己的操作系统了。
为啥这是个问题?
以前我们学过怎么让int?或者double?这种值类型能等于null——比如数据库字段没填的时候,明确表示“没有值”。
但引用类型(比如string、各种class、数组啥的)一直都能等于null。C#从一开始就是这样。方便归方便,但也危险——因为语言不会强制你去想“这个引用会不会是空的?”
2. 进化:Nullable Reference Types(NRT)
五年前,C#引入了一个新特性,直接颠覆了对抗NullReferenceException的思路——Nullable Reference Types(NRT)。
string s = "hello"; // 非nullable引用
string? maybe = null; // nullable引用
核心思想:
- 严格区分那些绝对不能为null的引用变量,和那些可以为空的引用。
- 让程序员在编译阶段就能看到null相关的潜在问题,而不是等到运行时才发现(那就晚了)。
新版C#里,声明引用变量的方式变了:默认不能直接赋null,除非你明确允许。
就这么一个?,变量的意义立马变了!
3. 开启Nullable Reference Types
默认情况下,这种严格语法是关闭的,为了兼容老代码。但现在Visual Studio、Rider和.NET CLI的新项目模板都已经默认开启NRT——或者至少强烈建议你这么做。
想知道你的项目有没有开NRT,去.csproj文件里找这行:
<Nullable>enable</Nullable>
没有的话可以手动加上——很安全。
对代码有啥影响?
- 如果NRT没开(老行为):string s = null;——没报错,大家都无所谓。
- 如果开了:编译器会警告你,不能把null塞进不该为null的变量。
4. NRT的例子
简单例子
#nullable enable // 这行代码为当前文件开启NRT检查
string notNullable = "你好";
string notNullable2 = null; // 编译错误!
string? nullableString = null; // 没问题,我们明确允许null
第一行声明了一个永远要指向真实对象的字符串。
第三行声明了一个可以为null的字符串。
检查null
void PrintLength(string? s)
{
// 编译器会警告你:“如果s == null咋办?”
Console.WriteLine(s.Length);
// 这样就OK
if (s != null)
{
Console.WriteLine(s.Length);
}
}
编译器现在会提醒你别忘了检查!
编译器警告
如果你无视警告,直接访问nullable变量——编译器会给你个新(而且很有用!)的警告。不是错误(程序还能编译),但会有个黄灯提醒你:“你确定吗,兄弟?”
对比不同模式:
| C#类型 | 能为null吗? | 传统模式(NRT前) | NRT模式(#nullable enable) |
|---|---|---|---|
| int | 不能 | 不能 | 不能 |
| int? | 能 | 能 | 能 |
| string | 能 | 不能(一直都能) | 不能(默认不行) |
| string? | 能 | 不能 | 能 |
5. 建议:NRT怎么用、为啥用
- 减少bug:少了null导致的莫名其妙崩溃,开发者和用户都开心。
- 代码更清晰:一眼就能看出哪里可能是空,哪里必须有值。
- 编译器帮忙:承认吧,编译器真的很努力!NRT警告是发现潜在bug的宝藏。
哪些场景必不可少
- 大项目,很多人一起写代码。
- API和公共库——让别人一看就知道能干啥不能干啥。
- 对可靠性要求高的地方(比如银行app、医疗系统啥的)
6. 常见错误和小陷阱
- “忘了加?”
给普通字符串赋null(string s = null;)——编译器会警告你,因为现在默认普通字符串不能为null。 - “乱用?”
所有变量都写成string?,只为了让编译器不警告。其实应该仔细标注,哪里才真的允许为空。 - “误解警告”
忽略警告,结果在以为编译器会帮你兜底的地方还是遇到NullReferenceException。
GO TO FULL VERSION