C# 中的 null 包容运算符 “!” —— 概念、由来、用法和注意事项

在 2020 年的最后一天,博客园发起了一个开源项目:基于 .NET 的博客引擎 fluss,我抽空把源码下载下来看了下,发现在属性的定义中,有很多地方都用到了 null!,如下图所示:

这是什么用法呢?之前没有在项目中用过,所以得空就研究了一下。

以前,! 运算符用来表示 “否”,比如不等于 !=。在 C# 8.0 以后,! 运算符有了一个新意义—— null 包容运算符,用来控制类型的可空性。要了解 null 包容运算符,首先就要了解可为 null 的引用类型

可为 null 的引用类型

C# 8.0 引入了可为 null 的引用类型,与可空类型补充值类型的方式一样,它们以相同的方式补充引用类型。也就是说,通过将 ? 追加到某引用类型,可以将变量声明为可以为 null 的引用类型。例如,string? 表示可以为 null 的 string。使用这些新类型可以更清楚地表达代码设计的意图 —— 比如将某些变量声明为 必须始终具有值,而其他一些变量声明为 可以缺少值

借助这个定义,我们在定义引用类型的变量或属性时,便有了两种选择:

  1. 假定引用不可以为 null 当变量定义为不可以为 null 时,编译器会强制执行规则——确保在不检查它们是否为 null 的前提下,取消引用这些变量是安全的:

  • 变量必须初始化为非 null 值。

  • 变量永远不能赋值为 null

  • 假定引用可以为 null 当变量定义为可以为 null 时,编译器会强制执行不同的规则——确保您自己已正确检查 null 引用:

    • 只有当编译器可以保证该值不为 null 时,才可以取消引用该变量。

    • 这些变量可以用默认的 null 值进行初始化,也可以在其他代码中赋值为 null

    与 C# 8.0 之前对引用变量的处理相比,这个新功能提供了显著的优势。在早期版本中,不能通过变量的声明来确定设计意图,编译器没有为引用类型提供针对 null 引用异常的安全性。

    通过添加可为 null 的引用类型,您可以更清楚地声明您的意图。null 值是表示一个变量不引用值的正确方法,请不要使用此功能从代码中删除所有的 null 值。而是,应向编译器和阅读代码的其他开发人员声明您的意图。通过声明意图,编译器会在您编写与该意图不一致的代码时警告您。

    是不是读起来有点绕?还是直接看示例比较容易理解些,请继续往下看。首先,我们来

    启用可为 null 的引用类型

    有三种方法可以启用可为 null 的引用类型

    在项目文件中启用

    <Nullable>enable</Nullable>
    

    将上面这一行添加到项目文件中,为当前项目启用 可为 null 的引用类型,如下图所示:

    在自定义项目属性中启用

    在 Directory.Build.props 文件中可以为目录下的所有项目启用 可为 null 的引用类型, 下面截图是 fluss 项目中的设置:

    使用预处理器指令启用

    可以使用 #nullable enable 和 #nullable disable 预处理器指令在代码中的任意位置启用和禁用 可为 null 的引用类型

    举例说明

    典型用法

    假设有这个定义:

    class Person
    {public string? MiddleName;
    }
    

    如下这样调用:

    void LogPerson(Person person)
    {Console.WriteLine(person.MiddleName.Length);  // 警告  CS8602  解引用可能出现空引用。Console.WriteLine(person.MiddleName!.Length); // 没有警告
    }
    

    这个 ! 运算符基本上就是关闭了编译器的空检查。

    内部运行机制

    使用此运算符告诉编译器可以安全地访问可能为 null 的内容。您可以用它来表达在这种情况下“不关心” null 安全性。

    当我们讨论到 null 安全性时,一个变量可以有两种状态:

    1. Nullable : 可以为 null

    2. Non-Nullable :不可以为 null

    从 C# 8.0 开始,所有的引用类型默认都是 Non-nullable

    “可空性”可以通过以下两个新的类型运算符进行修改:

    1. ! :从 Nullable 改为 Non-Nullable

    2. ? :从 Non-Nullable 改为 Nullable

    这两个运算符是相互对应的。您使用这两个运算符限定变量,然后编译器根据您的限定来确保 null 安全性。

    ? 运算符的用法

    1. Nullable:string? x;

    • x 是引用类型,因此默认是不可以为 null 的

    • 我们使用 ? 运算符将其改为可以为 null 的

    • x = null; 赋值正常,没有警告。

  • Non-Nullable:string y;

    • y 是引用类型,因此默认是不可以为 null 的

    • y = null; 赋值会产生一个警告,因为您给一个声明为不支持 null 的变量分配了一个 null 值。

    如下图:

    ! 运算符的用法

    string x;
    string? y = null;
    
    1. x = y;

    • 非法!警告:将 null 文本或可能的 null 值转换为不可为 null 类型(y 可能为 null)。

    • 赋值运算符 = 左边是不可以为 null 的,但右边是可以为 null 的

  • x = y!;

    • 合法!

    • 赋值运算符 = 左右两边都是不可以为 null 的

    • 因为 y! 使用了 ! 运算符到 y,使得右边也变成了不可以为 null 的,所以赋值没有问题。

    如下图:

    ⚠️ 警告: null 包容运算符 ! 仅在类型系统级别关闭编译器检查;在运行时,该值仍然可能是 null

    这是反模式的

    C# 编程时应该尽量避免使用 null 包容运算符 !

    有一些有效的使用场景(在下面会介绍),比如单元测试,使用这个运算符是适合的。不过,在 99% 的情况下,使用替代解决方案会更好。请不要只是为了取消警告,而在代码中打几十个 !。要想清楚您的场景是否真的值得使用它。

    ???? 可以使用,但要小心使用。如果没有实际的目的或使用场景,请不要使用它。

    null 包容运算符 ! 抵消了您获得的编译器保证的 null 安全性的作用!

    使用 ! 运算符将导致很难发现 bug。如果您定义了一个标记为不可以为 null 的属性,您也就假定了可以安全地使用它。但是在运行时,您却突然遇到 NullReferenceException 异常而挠头,因为一个值在用 ! 绕过了编译器检查后,实际上却变成了 null,这不是给自己添麻烦吗?

    既然这样,那么,

    为什么 ! 运算符会存在?

    • 在某些边缘情况下,编译器无法检测到可以为 null 的值实际上是不为 null 的。

    • 使遗留代码库迁移更容易。

    • 在某些情况下,您根本不关心某些内容是否为 null

    • 在进行单元测试时,您可能想要检查传递 null 时的代码行为。

    接下来,我们继续看下:

    null! 是什么意思呢?

    null! 是在告诉编译器 null 不是 null 值,这听起来很怪异,是不是?

    实际上,它和上面例子中的 y! 一样。它只是看起来挺怪异,因为您将该运算符用在了 null 字面量上,但概念是一样的。

    我们再来看一下文章开头提到的 fluss 源码中的一行代码:

    /// <summary>
    /// 所属的博客。
    /// </summary>
    public BlogSite BlogSite { get; set; } = null!;
    

    这行代码定义了一个名称为 BlogSite、类型为 BlogSite 的不可以为 null 的类属性。因为它是不可以为 null 的,因此单从技术上讲,很明显它是不可以被赋值为 null的。

    但是,您可以通过使用 ! 运算符,将 BlogSite 属性赋值为 null。因为,就编译器所关心的 null 安全性而言,null! 不是 null

    总结

    看到这里,想必您肯定已经明白了 null! 是什么意思,也学会了 null 包容运算符 ! 的概念、由来和用法。但是正如我在文中提到的那样,编程时应该尽量避免使用 !,因为它抵消了您本可以获得的编译器保证的 null 安全性;而且,这种写法阅读起来有点让人费解。


    参考:

    • https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references

    • https://stackoverflow.com/questions/54724304/what-does-null-statement-mean

    • https://www.cnblogs.com/cmt/p/14217355.html

    • https://www.meziantou.net/csharp-8-nullable-reference-types.htm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305836.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微前端架构在容器平台的应用

源宝导读&#xff1a;随着业务的发展&#xff0c;天际-星舟平台未来需要解决与其他云共创共建&#xff0c;跨团队高效协作等诸多问题&#xff0c;而星舟现有的技术架构将难以支撑。本文将介绍星舟平台如何通过向更先进的“微前端”架构演进落地&#xff0c;以应对将来快速增长的…

Microsoft PHP.Net ?

居然发现老外有个项目在把PHP搞成象。NET那样咯&#xff0c;核心是Framework 上把 PHP 编译为MSIL。居然见http://www.php-compiler.net/&#xff0c;而且今年居然还FINAL 1.0出来了&#xff0c;这对象把PHP放到.NET平台上来的人说是好消息。20 February 2006: Phalanger versi…

巧用 Lazy 解决.NET Core中的循环依赖关系

原文作者: Thomas Levesque 原文链接&#xff1a;https://thomaslevesque.com/2020/03/18/lazily-resolving-services-to-fix-circular-dependencies-in-net-core/循环依赖的问题在构建应用程序时&#xff0c;良好的设计应该应避免服务之间的循环依赖, 循环依赖是指某些组件直接…

java的编译器怎么出来_怎样掌握ava编译器的使用,教程在这里,如何进行Java初级学习...

原标题&#xff1a;怎样掌握ava编译器的使用&#xff0c;教程在这里&#xff0c;如何进行Java初级学习Java的学习中&#xff0c;并没有那么的繁琐&#xff0c;只需要我们逐步掌握&#xff0c;就能够发觉java是全世界最好的编程语言之一。那么今天就带领大家进行简单的JAVA初级学…

小试elsa

最近工作需要&#xff0c;在调研BMP产品&#xff08;开源和商用&#xff09;&#xff0c;重点了解了activiti和它的商业产品Alfresco Process Services&#xff0c;这是java的体系&#xff0c;成熟&#xff0c;完善(三方开源库是java多年开源积累下的最宝贵的财富)&#xff0c;…

java抠图人物背景图片_如何进行人物抠图?让你快速完成复杂背景人像的在线抠图...

大多数男生心目中都有一个女神&#xff0c;虽然在其他人眼中不过是普通人&#xff0c;但是在自己眼中她怎么看怎么有魅力。当然对于女神提出的各种各样的“要求”或是请求&#xff0c;你们定然是不会轻易拒绝的。但若是女神需要你帮忙抠图&#xff0c;你知道如何进行人物抠图吗…

防止多次提交的几个比较

1、利用Session(viewState是不行的&#xff0c;viewState要回传才能读到数据)如果有个数据包类StockBillMP&#xff0c;则设定privateStockBillMP M_saveobj { get { return (Session["saveobj"] null)?null:(StockBi…

【日常排雷】 .Net core 生产环境appsetting读取失败

关键词System.ArgumentNullException: String reference not set to an instance of a String. (Parameter s)1.问题出现某年某月某日&#xff0c;把webapi开发完了&#xff0c;也通过了swagger进行了单元测试。dotnet build dotnet publish -o publish dotnet .\publish\xx.We…

ABP vNext 自动注入,暗藏天坑如斯

导言我们在使用ABP vNext框架时&#xff0c;都知道该框架为我们实现了自动依赖注入(实现自动注入需要在项目里面创建Module类&#xff0c;并且将Module类上的DependsOn到相应的启动Module类或调用Module类&#xff0c;这步很关键)自动注入示例只要我们实现如下接口&#xff1a;…

Community Server系列之四:Ajax在CS2.0中的应用1

Ajax技术在时下很热门&#xff0c;当然在CS2.0中也不例外的运用到了此技术&#xff0c;CS2中没有使用任何第三方Ajax控件&#xff0c;这就给我们提供一个研究Ajax机理的好地方&#xff0c;现介绍一下在CS2中Ajax的一些简单应用&#xff0c;并分析应用的原理。 我想要研究此技术…

为什么人和人的差距这么大?

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份这是一篇去年写的旧文&#xff0c;不少读者从这篇文章中获得了一些方法和力量&#xff0c;于是再分享下&#xff1a;工作和生活中不光要埋头干活&#xff0c;还要抬头看天。思考总结方法论是提升认知的必备途…

github可以传java吗_如何在github上传本地项目代码(新手使用)----亲测使用

首先你要在github上申请一个账号然后你要下载一个git工具进入官网直接下载就行&#xff0c;下载完成后进入github首页&#xff0c;点击新项目new repository(新建)&#xff0c;如下图所示&#xff1a;然后进入如下页面&#xff0c;主要填写红色圈起来的几个部分&#xff0c;如下…

【One by One系列】IdentityServer4(一)OAuth2.0与OpenID Connect 1.0

在微服务场景中&#xff0c;身份认证通常是集中处理&#xff0c;这也是有别于单体应用一把梭哈的模式&#xff0c;其中&#xff0c;在微软微服务白皮书中&#xff0c;提供了两种身份认证模式&#xff1a;网关&#xff0c;没错&#xff0c;原话是If youre using an API Gateway,…

ABP vNext分布式事件总线RabbitMQ注意事项

[https://docs.abp.io/zh-Hans/abp/latest/Distributed-Event-Bus-RabbitMQ-Integration](ABP vNext官方文档链接)&#xff0c;基本使用可直接阅读官方文档&#xff0c;云怀不重复造轮子&#xff0c;只做官方未提到但重要的说明关键配置说明关键配置类&#xff1a;AbpRabbitMqE…

去除代码行号的一个小程序(控制台版本)

清风竹林发布了去除代码行号的一个小程序,确实方便大家收集一些文章代码,但个人认为象这样的小东东&#xff0c;要使广大网友能拿来就用&#xff0c;用.Net 2.0做成WinForm&#xff0c;有点贵族化了&#xff0c;于是动手整出个平民化的控制台版本&#xff0c;可以清除指定的文本…

. NET5实战千万高并发项目,性能吊打JAVA,C#排名万年老五,有望逆袭!

“秒杀活动”“抢红包”“微博热搜”“12306抢票”“共享单车拉新”等都是高并发的典型业务场景&#xff0c;那么如何解决这些业务场景背后的难点问题呢&#xff1f;秒杀系统中&#xff0c;QPS达到10万/s时&#xff0c;如何定位并解决业务瓶颈&#xff1f;明星婚恋话题不断引爆…

ABP vNext 审计日志获取真实客户端IP

背景在使用ABP vNext时&#xff0c;当需要记录审计日志时&#xff0c;我们按照https://docs.abp.io/zh-Hans/abp/latest/Audit-Logging配置即可开箱即用&#xff0c;然而在实际生产环境中&#xff0c;某些配置并不可取&#xff0c;比如今天的主角——客户端IP&#xff0c;记录用…

郭昶

郭 昶左直拳饰演《外来媳妇本地郎》中康家老二康祁宗的演员郭昶6月14日去世了&#xff0c;胃癌&#xff0c;享年50岁。这个消息真令人难以置信&#xff0c;不胜嘘唏。 《外来媳妇本地郎》在广东这边很受欢迎&#xff0c;每集结尾那带有浓厚岭南特色的粤曲小调在胡同小巷时有…

ABP vNext IOC替换原有Service实现

即 .NET IOC替换原有Service实现背景在使用ABP vNext时&#xff0c;该框架为我们实现了非常多的默认行为&#xff0c;以便开箱即用&#xff0c;但在实际使用中&#xff0c;我们总是需要根据自己的需求定制自己的服务&#xff0c;在.Net框架中&#xff0c;便提供了Service.Repla…

aqs java 简书,Java AQS源码解读

1、先聊点别的说实话&#xff0c;关于AQS的设计理念、实现、使用&#xff0c;我有打算写过一篇技术文章&#xff0c;但是在写完初稿后&#xff0c;发现掌握的还是模模糊糊的&#xff0c;模棱两可。痛定思痛&#xff0c;脚踏实地重新再来一遍。这次以 Java 8源码为基础进行解读。…