c# 8 引入了新特性:"可为空引用"(详情), 这个功能个人觉得挺好的, 能够非常明确的表现程序设计者的意图, 编译器能够进行检查, 尽最大可能减小 NullReferenceException 错误.
如果是新项目, 那么上手很简单, 一点点搭建起来, 遇山开山, 遇河渡河. 但是对于我这种手头上的项目大多都是以前创建的情况, 就要稍微做那边么一点操作了.
要看完整说明, 请查看开头的那个链接.
准备
首先评估一下几个条件:
项目可以基于. NET CORE 3.0 及以上编译. 如果不行, 那么就请直接右上角点 *.
是不是大多数的变量都需要 null 引用? 如果是的话, 个人觉得不值得费劲了.
操作
以一个 ASP.NET webAPI 为例, 项目修改前是能够正常编译无错误无警告的.
1. 启用 Nullable(可为空引用类型)
Nullable 默认是不启用的, 需要做一些修改以启用. 有两种方式:
修改 csproj 文件, 在 ProperyGroup 里面添加 enable 项.
对于比较小型的项目, 可以直接修改, 这样弹出来的警告或者错误会比较少, 方便我们快速改正.
使用编译器指令 #nullable enable 和 #nullable restore 进行修改. 在代码段的开头 enable, 结尾处 restore.
对于中大型项目, 直接使用第一种方式进行修改会导致大量的警告, 很容易一团糟; 可以通过编译器指令对单文件或者单类进行修改操作, 一点一点地修改.
2. 修改代码
我的项目使用第一种方法的的情况下有 24 个警告(编译后有 67 个), 也不知道算多还是算少.
实体类
- [DataContract]
- [Table("recordinfo")]
- public class RecordInfo : InfoBase
- {
- /// <summary>
- /// 记录 ID
- /// </summary>
- [DataMember]
- [Key]
- public string RecordNum { get; set; }
- /// <summary>
- /// 车辆 RFID 号码
- /// </summary>
- [DataMember]
- public string CarID { get; set; }
RecordNum 为主键, 通过 EF 进行映射, 结果也不会为 null, 所以声明应该保持原样即可. CarID 不是主键, 有可能是 null, 因此应当显式声明为 string?, 表示可以为空, 删除警告.
编译器检查, RecordNum 没有被初始化, 我们的设计意图告诉编译器了, 但是代码还没有保证这个不能为空, 因此需要修改代码保证 RecordNum 不为空.
这里使用 null 包容运算符 (!) 来进行操作, 提示编译器这个位置实际上不会为 null.
- //string 的 default 为 null, 通过增加! 告诉编译器, 这块初始化的时候实际上是不为空的.
- public string RecordNum {
- get; set;
- } = default!;
null 包容运算符并不能确保不是 null, 如果可以使用代码确保不为 null, 那么使用代码会是更优选择. 考虑如下代码:
- // 我经常使用 String.IsNullOrWhiteSpace 来进行检查, 空文本对我的业务没有意义, 因此适用.
- public string RecordNum {
- get; set;
- } = "";
特别提示:
可为空引用类型检查是编译器的行为, 它可以提供编译时检查, 但是不提供运行时检查, 如果使用外部代码调用, 那么是否为空都可以进行赋值.
很明显, 上面代码运行时也很难保证不是 null, 我们可以再改进一下.
- public string RecordNum
- {
- get => recordNum;
- set => recordNum = value ?? "";
- }
- private string recordNum = "";
官方推荐对 POCO 类使用构造函数保证不为空.
指定了 default! 的情况, ASP.NET CORE WEBAPI 会内部自动标注[Required], 远程调用如果缺失参数, 会提示 bad request.
DataContext 类
DataContext 也是类似的, 主要是 DbSet 对象的引用问题.
- //BaseDirectory 的返回是 string? 类型的
- var baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
- //Path.Combine()不接受 string?, 提示错误.
- var xmlPath = Path.Combine(baseDirectory, System.AppDomain.CurrentDomain.FriendlyName + ".xml");
- var baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
- if (baseDirectory == null) throw new ArgumentNullException("baseDirectory");
- var xmlPath = Path.Combine(baseDirectory, System.AppDomain.CurrentDomain.FriendlyName + ".xml");
- public class ReturnData<T>
- {
- // 整个类型会提示 Data 未能初始化, ErrorMsg 未能初始化.
- public ReturnData(){ }
- public ReturnData(T data) => Data = data;
- public ReturnData(string error) => ErrorMsg = error;
- /// <summary>
- /// 页面数据
- /// </summary>
- public T Data { get; set; }
- public string ErrorMsg { get; set; }
- }
- public class ReturnData<T>
- where T: class
- {
- public ReturnData(){ }
- public ReturnData(T data) => Data = data;
- public ReturnData(string error) => ErrorMsg = error;
- /// <summary>
- /// 页面数据
- /// </summary>
- public T? Data { get; set; }
- public string? ErrorMsg { get; set; }
- }
- using ManageDataContext context = new ManageDataContext();
- var props = contextType.GetProperty($"{namestring}s");
- //props 提示有可能为 null
- var dbset = (props.GetValue(context) as DbSet<T>);
- // 提示 dbset 可能为 null
- var res = await dbset.FindAsync(value);
- using ManageDataContext context = new ManageDataContext();
- var props = contextType.GetProperty($"{namestring}s");
- // 判断 props 可以解决问题.
- if (props == null) throw new ArgumentNullException("Props");
- var dbset = (props.GetValue(context) as DbSet<T>);
- // 判断 dbset 可以解决问题.
- if (dbset == null) throw new ArgumentNullException("dbset");
- var res = await dbset.FindAsync(value);
来源: https://www.cnblogs.com/podolski/p/12692888.html