.NET项目升级手记:可为空引用
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对象的引用问题。
来自.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<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点,帮助我们养成良好的编程习惯,也便于我们告诉其他人我们的设计意图。
编译器能帮我们做的工作,就没必要自己再费劲做了,懒的不行,我得歇会儿。
.NET项目升级手记:可为空引用的更多相关文章
- NET MVC1项目升级到MVC2最简单的方法
NET MVC1项目升级到MVC2最简单的方法 把MVC1项目升级到MVC2,最简单的做法如下: 新建MVC2项目 新建一个MVC2项目,把原来MVC1的项目文件全部拷贝到新建MVC2项目目录里,依照 ...
- [转]将某个Qt4项目升级到Qt5遇到的问题
原文:http://hi.baidu.com/xchinux/item/9044d8ce986accbb0d0a7b87晚上花了4个小时,将以前的一个项目从Qt 4.8.4-MinGW升级到了Qt5. ...
- 轻松把你的项目升级到PWA
什么是PWA PWA(Progressive Web Apps,渐进式网页应用)是Google在2015年推出的项目,致力于通过web app获得类似native app体验的网站. 优点 1.无需客 ...
- ASP.NET Core 实战:将 .NET Core 2.0 项目升级到 .NET Core 2.1
一.前言 最近一两个星期,加班,然后回去后弄自己的博客,把自己的电脑从 Windows 10 改到 Ubuntu 18.10 又弄回 Windows 10,原本计划的学习 Vue 中生命周期的相关知 ...
- 将 Unity5.3 的老项目升级到 Unity 2018.3 遇到的些许问题。
删除 ParticleEmmiter 等废弃的接口: 删除 WindowsSecurityContext System.Security.Principal.WindowsIdentity 在 .Ne ...
- Webpack + Vue 多页面项目升级 Webpack 4 以及打包优化
0. 前言 早在 2016 年我就发布过一篇关于在多页面下使用 Webpack + Vue 的配置的文章,当时也是我在做自己一个个人项目时遇到的配置问题,想到别人也可能遇到跟我同样的问题,就把配置的思 ...
- 将vue-cli 2.x的项目升级到3.x
尝试将vue-cli 2.x的项目升级到3.x,记录一下升级过程,和遇到的坑 1. 直接复制替换src文件夹 2. 安装项目需要的依赖 (可以将原来package.json dependencies下 ...
- 将arcEngine9.3和dev9.2.4开发的项目升级成arcObject10.2和dev15.1.3过程中遇到的问题和解决
好久没碰.net了,arcgis更是感觉都忘干净了,今天将arcEngine9.3和dev9.2.4开发的一个项目升级成arcObject10.2和dev15.1.3过程中遇到了一系问题,留个笔记,留 ...
- C# 利用.NET 升级助手将.NET Framework项目升级为.NET 6
概述 .NET6 正式版本已经发布有一阵子了,今天我就体验一下如何将.NET Framework的项目升级为.NET 6. 升级条件: Windows 操作系统 .NET 6 SDK Visual ...
随机推荐
- 基于Linux Lite 4.8制作“Windows 12 Lite”正式发布
值得注意的是,这款Windows 12 Lite的宣传标语将矛头直指Windows 10,声称不会迫使用户更新.不会存在病毒或恶意软件.启动速度比Win 10快3倍.提供8万多款优质软件.允许与Win ...
- SQL逗号合并一列多行的值
select stuff((select ','+行名 from 表名 for xml path('')),1,1,'')
- ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)
前言 上一篇讲解了 AQS 的独占锁部分(参看:ReentrantLock 源码分析以及 AQS (一)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadW ...
- 结题报告--洛谷P3915
题目:点此. 我处理这种多组数据的方法被我叫做“mains法”,就是先假设只有一组数据,写一个代码,然后把那个main函数改成mains,最后写一个真正的main函数. 这个“真正的”main函数一般 ...
- liunx 安装 zookeeper(转)
转自:https://www.cnblogs.com/expiator/p/9853378.html linux安装zookeeper及使用 一.安装条件 想要安装zookeeper,必须先在linu ...
- 998. 最大二叉树 II
最大树定义:一个树,其中每个节点的值都大于其子树中的任何其他值. 给出最大树的根节点 root. 就像之前的问题那样,给定的树是从表 A(root = Construct(A))递归地使用下述 Con ...
- 信息收集工具-dimtry
GitHub地址: kali下也是自带的: -s以及-e 参数需要用到Google搜索 1.获取whois主机ip信息 2.扫描端口,根据banner信息判断服务
- KMP 算法简单解释
讲KMP算法,离不开BF,实际上,KMP就是BF升级版,主要流程和BF一样 不同是在匹配失败时能利用子串的特征减少回溯,利用根据子串特征生成的Next数组来减少 <( ̄︶ ̄)↗[GO!] ...
- Keras实现RNN模型
博客作者:凌逆战 博客地址:https://www.cnblogs.com/LXP-Never/p/10940123.html 这篇文章主要介绍使用Keras框架来实现RNN家族模型,TensorFl ...
- .NET Core 3 WPF MVVM框架 Prism系列之区域管理器
本文将介绍如何在.NET Core3环境下使用MVVM框架Prism的使用区域管理器对于View的管理 一.区域管理器 我们在之前的Prism系列构建了一个标准式Prism项目,这篇文章将会讲解之前项 ...