.net get set 初始化_.NET项目升级:可为空引用

前言

C#8引入了新特性:可为空引用

https://docs.microsoft.com/zh-cn/dotnet/csharp/nullable-references

这个功能个人觉得挺好的,能够非常明确的表现程序设计者的意图,编译器能够进行检查,尽最大可能减小NullReferenceException错误。

如果是新项目,那么上手很简单,一点点搭建起来,遇山开山,遇河渡河。但是对于我这种手头上的项目大多都是以前创建的情况,就要稍微做那边么一点操作了。

要看完整说明,请查看开头的那个链接。

准备

首先评估一下几个条件:

1、项目可以基于.NET Core 3.0及以上编译。如果不行,那么就请直接右上角点×。

2、是不是大多数的变量都需要null引用?如果是的话,个人觉得不值得费劲了。

操作

以一个ASP.NET WebAPI为例,项目修改前是能够正常编译无错误无警告的。

c7c8eb9e9e7e83841e7ba202f2820368.png

1、启用Nullable(可为空引用类型)

Nullable默认是不启用的,需要做一些修改以启用。有两种方式:

修改csproj文件,在ProperyGroup里面添加enable项。

对于比较小型的项目,可以直接修改,这样弹出来的警告或者错误会比较少,方便我们快速改正。

使用编译器指令#nullable enable和#nullable restore进行修改。在代码段的开头enable,结尾处restore。

对于中大型项目,直接使用第一种方式进行修改会导致大量的警告,很容易一团糟;可以通过编译器指令对单文件或者单类进行修改操作,一点一点地修改。

2、修改代码

我的项目使用第一种方法的的情况下有24个警告(编译后有67个),也不知道算多还是算少。

f93a9947edf1e1a2ffe8cfe4a9fef282.png

实体类

[DataContract][Table("recordinfo")]public class RecordInfo : InfoBase{    ///     /// 记录ID    ///     [DataMember]    [Key]    public string RecordNum { get; set; }    ///     /// 车辆RFID号码    ///     [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对象的引用问题。

来自.NET Class Library

//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{    //整个类型会提示Data未能初始化,ErrorMsg未能初始化。    public ReturnData(){ }    public ReturnData(T data) => Data = data;    public ReturnData(string error) => ErrorMsg = error;    ///     /// 页面数据    ///     public T Data { get; set; }    public string ErrorMsg { get; set; }}

设计意图:Data与ErrorMsg不同时为空,也不同时有值。

基于设计,可以做如下修改。注意添加了class约束。

public class ReturnData where T: class{    public ReturnData(){ }    public ReturnData(T data) => Data = data;    public ReturnData(string error) => ErrorMsg = error;    ///     /// 页面数据    ///     public T? Data { get; set; }    public string? ErrorMsg { get; set; }}

其他例子

using ManageDataContext context = new ManageDataContext();var props = contextType.GetProperty($"{namestring}s");//props提示有可能为nullvar dbset = (props.GetValue(context) as DbSet);//提示dbset可能为nullvar 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);//判断dbset可以解决问题。if (dbset == null) throw new ArgumentNullException("dbset");var res = await dbset.FindAsync(value);

注意,将as替换为强制转换,并不能消除警告。

总结

最后消除了所有的警告,改造结束。

这个新的语言特性可以帮助我们发现一些潜在的bug点,帮助我们养成良好的编程习惯,也便于我们告诉其他人我们的设计意图。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值