NullReferenceException,就不应该存在!
如果要你说出 .NET 中的三个异常,NullReferenceException
一定会成为其中一个;如果说出 .NET 中的一个异常,NullReferenceException
也会被大多数人说出来。它让这么多人印象深刻,是因为它在项目中实在是太常见了,常见到每一个 C#/.NET 入门者必然会遇到。
然而,这个异常本不应该存在!
NullReferenceException 的可恨之处
你说 NullReferenceException
可以告诉你程序中某个字段为 null,告诉你程序发生了 BUG。
可是这是真的吗?说真的一定是因为用 Visual Studio 调试了,Visual Studio 告诉了我们异常发生在哪一句,哪个字段为 null。然而从真实用户或其他日志那里收集回来的数据是没有也不可能有这些信息的。这是因为 NullReferenceException
异常除了调用栈(StackTrace)之外不能提供其他额外的异常信息,连变量或字段名都不能提供。于是,当从异常日志准备分析异常原因的时候,只能猜,猜到底为 null
的是谁!
另外,NullReferenceException
异常发生的地方一定不是真正出错的地方!因为我们尝试去调用某个属性或方法时假设了它不为 null
,这意味着它为 null
就是个错误。但是,从异常的调用栈中我们却找不到任何痕迹能够告诉我们是哪里给它设置成了 null
(或者是从未赋值过)。现在,又只能猜,猜到底是什么时候通过什么方式将字段设为了 null
!
举个例子:
public class Walterlv
{
private string _value;
public void SetValue(string value)
{
_value = value;
}
public void DoSomething()
{
Console.WriteLine(_value.Length);
}
}
SetValue
可以在任何时候被任何方法调用,指不定某个时候 _value
就被设为 null
了。那么 DoSomething
被调用的时候,直接就会抛出 NullReferenceException
。这个方法比较简单,我们猜 _value
为 null
基本不会有问题了,方法复杂一点儿就难猜了。然而真正让 _value
为 null
的罪魁祸首就找不到了,因为它发生在 SetValue
中。
总结起来,可恨之处有亮点:
- 不能知道为
null
的是哪个变量、字段或属性; - 不能知道为什么为
null
。
而这两点直接与异常机制相悖。异常就是要提供足够我们诊断错误的信息,让我们在开发中避免发生这样的错误。
NullReferenceException 的替代方案
既然 NullReferenceException
没能给我们提供足够的信息,那么我们就自己来提供这些信息。
ArgumentNullException
就是一个不错的替代异常,说它好因为有两点:
- 在错误发生的最开始就报告了错误,避免错误的蔓延。
因为SetValue
中发生了异常后,获取到的调用栈是导致_value
为null
的调用栈。 - 告知了为
null
的参数名称。
靠以上两点,当发生异常时,我们能唯一确定 _value
为 null
的原因,而这才是本质错误。
可是,如果并不是参数问题导致了 null
,那我们还能用什么异常呢?InvalidOperationException
是个不错的方案,它的默认异常提示语是“对象当前的状态使得该操作无效”。当程序此时此刻的状态让我们获取不到某个数据致使数据为 null
时,可以写一个新的提示语告知此时到底是什么样的状态错误才使得获取到的数据为 null
。当然,这比 ArgumentNullException
的信息准确性还是差了点儿。
当然,还有一个替代方案,就是在 Console.WriteLine(_value.Length);
之前先对 _value
进行 null
判断。可是,你能说出 _value
为 null
代表什么意义吗?为什么为 null
时不应该输出?如果这个问题回答不上来,那么你的这个 null
判断为你的程序埋藏了一个更深的 BUG——当用户反馈软件行为不正常时,你甚至连异常信息都没收集到!硕大一个程序,你甚至都无法定位到底是哪个模块发生了错误!!!
对待 null,建议的约定
当了解了 NullReferenceException
的缺陷,再了解了其替代方案后,其实我们会发现一个问题:
- 其实多数时候根本就不应该存在
null
null
带来了两个困惑:
- 意义不明确。相比于异常,
null
并不能告知我们到底发生了什么。 - 使用方不知道究竟应不应该判空,也难以理清楚判空究竟意味着什么。
所以,为了解决这些困惑,我建议在开发中以如下方式对待我们的 null
:
- 对任何可被外部模块调用的方法的参数进行
null
判断,并在参数为null
时抛出ArgumentNullException
。 - 不要在方法中返回
null
。如果你无法根据现有状态完成方法承诺的任务,请抛出具体的异常并给出真实的原因。 - 如果确实要用
null
在程序中代表某种状态,请确定这能够代表某种唯一确定的状态,并强制要求使用方判空。
其中,对于第 2 点,不用担心异常导致雪崩,因为 try-catch-finally
就是用来恢复错误防止雪崩的,在需要防止雪崩的地方恢复错误即可。但要注意异常依然需要报告,可由程序统一处理这些未经处理的异常。
对于第 3 点,JetBrains
为我们提供了 JetBrains.Annotations
,这是一组 100+ 个的 Attribute
,以 NuGet 包的形式提供。强烈建议在 null
代表了某种特殊意义的地方标记 [CanBeNull]
;这样,ReSharper 插件将提醒我们这些地方必须要进行判空。C# 8.0 极有可能为我们带来“可空引用类型”或者“非空引用类型”;如果真的带来了,这将比 JetBrains.Annotations
拥有更大的强制性,帮助我们避免出现意外的 null
引用,帮助我们在可能为 null
的地方强制判空。再次重申:我们使用 null
一定是因为它代表了某种确定的特殊含义,而不是代表了一堆不明所以的错误!
NullReferenceException,就不应该存在!的更多相关文章
- 关于SubSonic3.0插件使用SqlQuery或Select查询时产生的System.NullReferenceException异常修复
早上在编写执行用例时,突然爆异常System.NullReferenceException: 未将对象引用设置到对象的实例 执行代码:
- 关于SubSonic3.0查询或更新时出现System.NullReferenceException异常的处理
在调试程序时,同事发现添加记录时,出现了System.NullReferenceException异常 DictBase dict = new DictBase(); dict.DictCode ...
- NullReferenceException UnityEngine.Transform.get_localPosition
NullReferenceException UnityEngine.Transform.get_localPosition unity程序中,需要取得GO自身的Transform,出现如上空异常, ...
- 【转】NGUI创建UIRoot后报NullReferenceException的解决办法
本文参考自 http://forum.china.unity3d.com/thread-1099-1-1.html 使用NGUI版本3.7.5. 在创建了一个UIRoot后,有时会报NullRefer ...
- C# WinForm程序添加引用后调用静态方法时报“Interfaces_Helper.Global”的类型初始值设定项引发异常。---> System.NullReferenceException: 未将对象引用设置到对象的实例。
出现原因: 因为Global类初始化某个静态变量时没有成功则会抛 System.NullReferenceException 异常,具体代码: public static string connstr ...
- CoreCLR源码探索(六) NullReferenceException是如何发生的
NullReferenceException可能是.Net程序员遇到最多的例外了, 这个例外发生的如此频繁, 以至于人们付出了巨大的努力来使用各种特性和约束试图防止它发生, 但时至今日它仍然让很多程序 ...
- .NET[C#]中NullReferenceException(未将对象引用到实例)是什么问题?如何修复处理?(转)
.NET[C#]中NullReferenceException(未将对象引用到实例)是什么问题?如何修复处理? 后端开发 作者: Rector 1973 阅读 0 评论 0 收藏 收藏本文 ...
- 【错误总结1:unity StartCoroutine 报 NullReferenceException 错误】
今天在一个项目中,写了一个单例的全局类,该类的作用是使用协程加载场景.但在StartCoroutine 这一步报了NullReferenceException 的错.仔细分析和搜索之后,得到错误原因. ...
- NullReferenceException 的可恨之处
通常我们在取数据库记录或者字段时,获取不存在的值时,会出现 NullReferenceException 如果根据某个键值去LoadById, 我们通常会检查一下这个键值是否在数据库里存在. 但如果I ...
- 程序运行在.Net 4.0低版本上 报“System.NullReferenceException”错误
因为程序仅在个别机器上出现“ System.NullReferenceException”问题,而在其他机器上一切运行正常,所以认为是环境问题 具体错误信息如下: 2018-09-14 10:12:1 ...
随机推荐
- TCGA学习1--下载level3 level4数据
1.使用firehose_get 下载level3 level4数据 https://confluence.broadinstitute.org/display/GDAC/Download exa ...
- 读取文件并找出年龄最大的N个人-兰亭集市笔试题
C++ code: #include <iostream> #include <fstream> #include <map> #include <strin ...
- SPOJ8222 NSUBSTR - Substrings
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- 无法启动此程序,因为计算机丢失MSVCP120.dll
这种错误是由于未安装** vcredist **引起的(而且版本是 2013版):https://www.microsoft.com/zh-CN/download/details.aspx?id=40 ...
- MVVM架构简单使用
版权声明:本文为博主原创文章,未经博主授权不得转载. 项目github地址 https://github.com/zhangjiahuan8888/mvvmDemo/tree/master 开篇 MV ...
- Angular2 中的依赖包详解
转自:http://blog.csdn.net/feiying008/article/details/53033704 目录 dependencies 和 devDependencies depend ...
- go-statsd项目
linux命令: 进程:top 收包丢包:netstat -su[c持续输出] go tool pprof: 我们可以使用go tool pprof命令来交互式的访问概要文件的内容.命令将会分析指定的 ...
- PHP表单(get,post)提交方式
PHP 表单处理 PHP 超全局变量 $_GET 和 $_POST 用于收集表单数据(form-data). $_GET 是通过 URL 参数传递到当前脚本的变量数组. $_POST 是通过 HTTP ...
- day24 Restful api 设计和CRM 客户关系管理
博客: Restful: http://www.cnblogs.com/alex3714/articles/6808013.html http://www.cnblogs.com/alex3714/a ...
- 常用js、jquery 语句(句型)
1.动态更改设置属性(class style 都是属性) $("#sendPhoneNum").attr("class", "n_input3&qu ...