什么是最快的:空合并运算符与 GetValueOrDefault 与条件运算符

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前, 星球 内第2个项目《仿小红书(微服务架构)》正在更新中。第1个项目:全栈前后端分离博客项目已经完结,演示地址:http://116.62.199.48/。采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 255 小节,累计 39w+ 字,讲解图:1716 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 1300+ 小伙伴加入,欢迎点击围观

我的文章 .net 的 15 个未充分利用的功能 引发了一场有趣的讨论。我很想知道哪种方法 更快 —— ?? (空合并运算符) getvalueordefault 方法或 ?:(条件运算符) 。最近我在堆栈溢出中读到,大多数人认为 getvalueordefault 方法是这三个方法中最快的。但是,我决定进行研究。我不是要 微优化 。我认为在 99% 的情况下,您将使用三种方法中的哪一种并不重要。通常,您应该选择更容易维护的那个。我不打算争论哪一个更具可读性,因为那是另一个话题。相反,我将向您介绍我的研究基准结果。

空合并运算符 ??

??运算符 如果不为空则返回左操作数,否则返回右操作数。可空类型可以包含一个值,也可以是未定义的。 ??运算符 定义将可空类型分配给不可空类型时要返回的默认值。


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

官方文档 https://msdn.microsoft.com/en-us/library/ms173224.aspx

获取值或默认方法

检索当前 nullable<t> 对象的值,或对象的默认值。它比 ??操作员。


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

如果您没有指定默认值作为方法的参数,则将使用所用类型的默认值。

官方文档: https://msdn.microsoft.com/en-us/library/72cec0e0 (v=vs.110).aspx

条件运算符 ?:

条件运算符 ( ?:) 根据布尔表达式的值返回两个值之一。以下是条件运算符的语法。

健康)状况 ?第一个表达式:第二个表达式;

条件必须评估为真或假。如果条件为真,则计算 first_expression 并成为结果。如果条件为假,则对 second_expression 求值并成为结果。仅评估两个表达式中的一个。


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

官方文档: https://msdn.microsoft.com/en-us/library/ty67wk28.aspx

getvalueordefault 和 null 合并运算符内部结构

您可以在以下 网址 找到 getvalueordefault 方法的源代码。该方法有两种重载,一种没有参数,另一种需要在变量为 null 时返回默认值。


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

如代码所示,在幕后,getvalueordefault 方法使用条件运算符。

所有这些对我来说还不够,所以我反编译了以下代码以了解它是如何翻译成 通用中间语言 (cil) 的。对于这项工作,我使用了免费的 telerik .net 反编译器 - telerik justdecompile


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

获取值或默认值


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

空合并运算符 cil


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

就我能应付的 cil 代码而言,我认为 x ?? y 转换为 x.hasvalue ? x.getvalueordefault() : y.这自动应该意味着前者很可能会比后者快得多。

哪个工作得更快 - 空合并运算符或 getvalueordefault 或条件运算符

为了对不同的测试用例进行基准测试,我创建了一个专门的分析器类。


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

所有量规均在发布配置中执行。编写基准测试时使用的正确工具是 system.diagnostics 命名空间中的 秒表 。 (我注意到这个命名空间的名字很好;这里的一切都对诊断问题很有用)。 datetime.now 是错误的工作工具,它旨在解决不同的问题。它比秒表更难使用,而且精度要低数千或数百万倍。在用 C# 编写基准时完全避免它。

不考虑 gc 成本可能会导致您无法衡量操作的真实成本,或者导致您将该成本计入错误的代码。在进行基准测试时,特别是比较基准测试时,我通常做的是强制垃圾收集器在每次测试前后对所有三代进行完整收集。

要强制垃圾收集器执行完整收集,请使用以下代码:


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

这些是我进行基准测试的六个测试用例。


 int? x = null;
int y = x ?? -1;
console.writeline("y now equals -1 because x was null => {0}", y);
int i = defaultvalueoperatortest.getnullableint() ?? default(int);
console.writeline("i equals now 0 because getnullableint() returned null => {0}", i);
string s = defaultvalueoperatortest.getstringvalue();
console.writeline("returns 'unspecified' because s is null => {0}", s ?? "unspecified");

执行的测试用例

(1.) 设置默认值的getvalueordefault

(2.) 具有默认值集的空合并运算符

(3.) 设置了默认值的条件运算符

(4.) 没有默认值的getvalueordefault

(5.) 空合并运算符返回默认值 0

(6.) 返回默认值 0 的条件运算符

自制基准测试结果

经过几次测试运行后,您可以查看我的研究结果。

此外,我还创建了两个比较图表,以便更好地可视化结果。请记住,我排除了第三个测试用例的结果,因为我不知道为什么,但是这个用例比其他用例慢得多。

在图表下方找到包含所有测试用例的平均执行时间的图表。

在图表下方找到包含所有测试用例的平均滴答声的图表。

正如您从我的结果中看到的那样,当您想要返回一个与当前可空类型的默认值不同的默认值时,性能最好的是 空合并运算符 (??) 。然而,当你想返回类型的默认值时, getvalueordefault 方法要快一点。

通过 telerik justtrace 进行专业基准测试

我自制的基准测试结果对我来说还不够,所以我安装了 telerik justtrace (用于 .net 和本机应用程序的二合一内存和性能分析器)。相同测试用例的结果略有不同。

对于返回不同的默认值, getordefaultvalue 方法比 null 合并运算符快 8% 以上。 此外,它在返回可空类型的默认值的测试用例中再次快了一点。

到目前为止在 c# 系列中

1. 实现复制粘贴c#代码

2. msbuild tcp ip logger c#代码

3. windows注册表读写c#代码

4. 在运行时更改 .config 文件 c# 代码

5. 通用属性验证器 C# 代码

6. 减少 automapper- 自动映射对象快 180%

7. C# 6.0 中的 7 个很酷的新特性

8. 代码覆盖类型——C#中的例子

9. mstest 通过 mstest.exe 包装器应用程序重新运行失败的测试

10. 在visual studio中有效安排使用的提示

11. 19 个必须知道的 visual studio 键盘快捷键——第 1 部分

12. 19 个必须知道的 visual studio 键盘快捷键——第 2 部分

13. 在visual studio中根据构建配置指定程序集引用

14. .net 的 15 个未充分利用的功能

15. .net 的前 15 个未充分利用的功能第 2 部分

16. 在 C# 中轻松格式化货币的巧妙技巧

17. 以正确的方式断言日期时间 mstest nunit c# 代码

18. 哪个工作得更快——空合并运算符或 getvalueordefault 或条件运算符

源代码

您可以从我的 github 存储库 下载完整的源代码。

如果您喜欢我的出版物,请随时 订阅

另外,点击这些分享按钮。 谢谢你!

参考