未将对象引用设置到对象的实例

作为C#程序员,肯定对“未将对象引用设置到对象的实例”这样的报错很熟悉,每个人(不是几乎)都肯定遇到过这个异常,而很多时候,解决方案也非常简单,在访问这个对象之前加一个“是否为空”的检查,类似

invoice.payment()   //忘记检查invoice是否为空了

//解决方案也就是在访问成员前加上检查
if(invoice == null) {
    //notice, exception, return, etc
}

invoice.payment()

Null的两种语义

出现null通常有两种情况:

对于第二种情况,正确的做法是使用异常(Exception),这也正是BCL常见的做法,比如在看MSDN上的某个函数文档的时候你会发现专门有一节就是Exceptions,用来列出可能会出现的异常,比如下面是WebClient.DownloadString的Exceptions文档内存:

exceptions.png

这个表示当你调用DownloadString方法的时候你需要显示检查着三种异常。但是异常是很重的装备,没有多少人会想自己手动这么细致的处理异常,也就是导致了:要么我们不检查异常,要么我们try-catch-all来捕获所有的异常。

值类型和引用类型

CSharp 8.0 可空值类型(Nullable Reference Type)

在C#的8.0版本中设计者统一了值类型和引用类型的可空处理:

这相当于一个Breaking changes,所以默认的Visual Studio中的C#静态分析器会将可能是引用类型可能为null的标记为warning,可以在设置中打开和关闭这样的warning

最终达到的目标就是你在程序中检查了可能为null的地方,直到你显示(explict)地标记一个引用类型可能为null。相当于说:编译器和分析器你别管了,风险我来担。而不是“偷偷”地将这些风险推延到运行时触发:未将对象引用设置到对象的实例。

为什么在报空异常的时候难以调试

当发生空异常的时候,我们希望知道是哪一行代码,哪个对象导致的NRE(Null Reference Exception)空异常,但是在生产环境中,我们只能得到调用栈信息,而没有更多的上下文给我们判断到底是哪个对象导致的空引用异常。如果导致异常的方法特别大,我们其实很难判断到底是哪里出现了异常。

为什么出现空异常没有给我们更到的调式信息?

为什么编译器不在访问前插入指令来防止这样的异常? 理论上是可以的,但是这会导致生成低效的代码,编译器在这里确实可以给到帮助,比如C# 8.0的可空类型就是帮助我们在编译期间找到这些可能出现的空引用异常。

项目中我们怎么防止空引用异常? 我个人推荐的还是显式检查异常,当然你可以打开C# 8.0的可空类型检查,但是这样的方式在实践中会出现:

显式空引用检查,可以使用下面一些类库:

@ 2018-06-04 10:34

Comments:

Sharing your thoughts: