c# 8引入了新特性:“可为空引用”(详情),这个功能我的以为挺好的,可以很是明确的表现程序设计者的意图,编译器可以进行检查,尽最大可能减少NullReferenceException错误。编程
若是是新项目,那么上手很简单,一点点搭建起来,遇山开山,遇河渡河。可是对于我这种手头上的项目大多都是之前建立的状况,就要稍微作那边么一点操做了。c#
要看完整说明,请查看开头的那个连接。函数
首先评估一下几个条件:ui
以一个ASP.NET WEBAPI为例,项目修改前是可以正常编译无错误无警告的。
设计
Nullable默认是不启用的,须要作一些修改以启用。有两种方式:rest
对于比较小型的项目,能够直接修改,这样弹出来的警告或者错误会比较少,方便咱们快速改正。code
对于中大型项目,直接使用第一种方式进行修改会致使大量的警告,很容易一团糟;能够经过编译器指令对单文件或者单类进行修改操做,一点一点地修改。xml
个人项目使用第一种方法的的状况下有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?,表示能够为空,删除警告。blog
编译器检查,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也是相似的,主要是DbSet对象的引用问题。
//BaseDirectory的返回是string?类型的 var baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory; //Path.Combine()不接受string?,提示错误。 var xmlPath = Path.Combine(baseDirectory, System.AppDomain.CurrentDomain.FriendlyName + ".xml");
这是一个潜在的bug点,对于以上代码,很显然BaseDirectory的返回为null不符合咱们的设计,咱们能够进行以下改造。
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; } }
设计意图:Data与ErrorMsg不一样时为空,也不一样时有值。
基于设计,能够作以下修改。注意添加了class约束。
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);
注意,将as替换为强制转换,并不能消除警告。
最后消除了全部的警告,改造结束。
这个新的语言特性能够帮助咱们发现一些潜在的bug点,帮助咱们养成良好的编程习惯,也便于咱们告诉其余人咱们的设计意图。
编译器能帮咱们作的工做,就不必本身再费劲作了,懒的不行,我得歇会儿。