哪个工作更快 - Null 合并运算符、GetValueOrDefault 还是条件运算符





5.00/5 (7投票s)
基准测试,哪种方法在获取可空类型的默认值时速度更快——Null 合并运算符、GetValueOrDefault 还是条件运算符。
引言
在我关于 .NET 中 15 个被低估的功能 的文章引起了有趣的讨论。我想知道哪种方法 更快 – ?? (null 合并运算符)、 GetValueOrDefault 方法还是 ?: (条件运算符)。最近我在 Stack Overflow 上读到,大多数人认为 GetValueOrDefault 方法在这三种方法中最快。然而,我决定自己做研究。我无意 进行微优化。我认为在 99% 的情况下,使用这三种方法中的哪一种都不会有影响。通常,你应该选择最容易维护的那种。我不会争论哪种方法更具可读性,因为这是另一个话题。我将向你展示我的研究的基准测试结果。

Null 合并运算符 ??
?? 运算符 在左侧操作数不为 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");
官方文档: https://msdn.microsoft.com/en-us/library/ms173224.aspx
GetValueOrDefault 方法
检索当前 Nullable<T> 对象的值,或对象的默认值。它比 ?? 运算符快。
float? yourSingle = -1.0f;
Console.WriteLine(yourSingle.GetValueOrDefault());
yourSingle = null;
Console.WriteLine(yourSingle.GetValueOrDefault());
// assign different default value
Console.WriteLine(yourSingle.GetValueOrDefault(-2.4f));
// returns the same result as the above statement
Console.WriteLine(yourSingle ?? -2.4f);
如果你不将默认值作为参数传递给该方法,则将使用该类型的默认值。
官方文档: https://msdn.microsoft.com/en-us/library/72cec0e0(v=vs.110).aspx
条件运算符 ?
条件运算符 (?:) 根据布尔表达式的值返回两个值中的一个。以下是条件运算符的语法。
condition ? first_expression : second_expression;
condition 必须计算为 true 或 false。如果 condition 为 true,则 first_expression 被计算并成为结果。如果 condition 为 false,则 second_expression 被计算并成为结果。两个表达式中只有一个会被计算。
int input = Convert.ToInt32(Console.ReadLine());
// ?: conditional operator.
string classify = (input > 0) ? "positive" : "negative";
官方文档: https://msdn.microsoft.com/en-us/library/ty67wk28.aspx
GetValueOrDefault 和 Null 合并运算符内部
你可以在以下 URL 上找到 GetValueOrDefault 方法的源代码。该方法有两种重载,一种没有参数,一种需要指定变量为 null 时返回的默认值。
如代码所示,GetValueOrDefault 方法在底层使用了条件运算符。
所有这些对我来说还不够,所以我反编译了以下代码,以找出它如何被翻译成 通用中间语言 (CIL)。为此,我使用了免费的 Telerik .NET 反编译器 - Telerik JustDecompile。
GetValueOrDefault CIL
Null 合并运算符 CIL
据我所能理解的 CIL 代码,我认为 x ?? y 被转换为 x.HasValue ? x.GetValueOrDefault() : y。这应该自动意味着前者很可能比后者快得多。
哪个工作更快 - Null 合并运算符、GetValueOrDefault 还是条件运算符
为了对不同的测试用例进行基准测试,我创建了一个专门的分析器类。
所有测量均在 Release 配置下进行。编写基准测试时使用的正确工具是 System.Diagnostics 命名空间中的 Stopwatch。 (我注意到这个命名空间起得很好;其中的一切对于诊断问题都很有用)。DateTime.Now 是错误的处理方式,它是为解决不同问题而设计的。它比 Stopwatch 难用,并且精度低数千到数百万倍。在 C# 中编写基准测试时,请完全避免使用它。
未能考虑 GC (垃圾回收) 开销会导致你无法测量操作的真实成本,或导致你将成本归咎于错误的代码。在进行基准测试时,尤其是进行比较性基准测试时,我通常的做法是在每次测试之前和之后强制垃圾回收器对所有三个代进行完整回收。
要强制垃圾回收器进行完整回收,请使用以下代码
GC.Collect();
GC.WaitForPendingFinalizers();
以下是我进行基准测试的六个测试用例。
执行的测试用例
- GetValueOrDefault 设置了默认值
- Null 合并运算符设置了默认值
- 条件运算符设置了默认值
- GetValueOrDefault 未设置默认值
- Null 合并运算符返回默认值 0
- 条件运算符返回默认值 0

自制基准测试结果
经过多次测试运行后,你可以查看我的研究结果。
NullCoalescing | GetValueOrDefault | ConditionalOperator | ||||
时间 毫秒 | 滴答 | 时间 毫秒 | 滴答 | 时间 毫秒 | 滴答 | |
6590.08 | 65900785 | 12834.06 | 128340559 | 2868229.54 | 28682295356 | |
6550.64 | 65506400 | 12515.2 | 125152037 | 2762583.84 | 27625838427 | |
6512.58 | 65125820 | 12703.32 | 127033235 | 2776872.61 | 27768726079 | |
6612.16 | 66121646 | 13019.02 | 130190178 | 2744443.96 | 27444439560 | |
6623.67 | 66236731 | 12716.64 | 127166364 | 2750357.54 | 27503575389 | |
6503.09 | 65030932 | 12760.48 | 127604785 | 2757157.9 | 27571578966 | |
6479.25 | 64792548 | 12499.89 | 124998868 | 2737087.32 | 27370873195 | |
6521.75 | 65217529 | 12679.25 | 126792490 | 2753856.27 | 27538562686 | |
6540.68 | 65406786 | 12814.12 | 128141196 | 2754010.95 | 27540109549 | |
6617.96 | 66179633 | 13043.48 | 130434765 | 2741872.25 | 27418722520 | |
6555.186 | 65551881 | 12758.546 | 127585448 | 2764647.218 | 27646472173 | Average |
ConditionalOperator Zero | GetValueOrDefault Zero | NullCoalescing Zero | ||||
时间 毫秒 | 滴答 | 时间 毫秒 | 滴答 | 时间 毫秒 | 滴答 | |
3915.94 | 39159371 | 3882.08 | 38820849 | 4527.83 | 45278308 | |
3890.69 | 38906893 | 3853.21 | 38532131 | 4493.96 | 44939560 | |
3891.92 | 38919243 | 3900.99 | 39009895 | 4588.62 | 45886167 | |
3933.29 | 39332895 | 3825.76 | 38257618 | 4627.5 | 46274951 | |
3880.38 | 38803838 | 3824.2 | 38241964 | 4624.89 | 46248852 | |
4249.6 | 42496035 | 4020.71 | 40207055 | 4732.86 | 47328587 | |
3978.69 | 39786865 | 4029.73 | 40297288 | 4590.46 | 45904620 | |
3964.34 | 39643393 | 3966.89 | 39668937 | 4769 | 47689954 | |
4004.05 | 40040469 | 3938.66 | 39386556 | 4627.1 | 46270958 | |
3862.22 | 38622233 | 3882.85 | 38828455 | 4521.96 | 45219593 | |
3957.112 | 39571123.5 | 3912.508 | 39125074.8 | 4610.418 | 46104155 | Average |
此外,我还创建了两个比较图表以便更好地可视化结果。请记住,我排除了第三个测试用例的结果,因为我不知道为什么,但这个用例比其他的慢得多。
以下是包含所有测试用例平均执行时间的图表。(放大或在新标签页中打开以查看完整尺寸)

以下是包含所有测试用例平均滴答数的图表。(放大或在新标签页中打开以查看完整尺寸)

从我的结果中可以看出,当你想要返回与当前 Nullable 类型默认值不同的默认值时,最佳执行者是 null 合并运算符 (??)。但是,当你想要返回类型的默认值时,GetValueOrDefault 方法会稍快一些。
通过 Telerik JustTrace 进行专业基准测试
我自制的基准测试结果还不够,所以我安装了 Telerik JustTrace (用于 .NET 和原生应用程序的 2 合 1 内存和性能分析器)。相同测试用例的结果略有不同。(放大或在新标签页中打开以查看完整尺寸)

对于返回不同的默认值,GetValueOrDefault 方法比 null 合并运算符 快了 8% 以上。同样,在返回可空类型的默认值的测试用例中,它也再次稍快一些。
C# 系列到目前为止
1. 实现复制粘贴 C# 代码
2. MSBuild TCP IP 日志记录器 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 中 using 语句的技巧
11. 19 个必须知道的 Visual Studio 键盘快捷键 - 第 1 部分
12. 19 个必须知道的 Visual Studio 键盘快捷键 - 第 2 部分
13. 在 Visual Studio 中根据生成配置指定程序集引用
14. .NET 中 15 个被低估的功能
15. .NET 中 15 个被低估的功能(第 2 部分)
16. 在 C# 中轻松格式化货币的技巧
17. 正确断言 DateTime 的 MSTest NUnit C# 代码
18. 哪个工作更快 - Null 合并运算符、GetValueOrDefault 还是条件运算符
19. 基于规范的测试设计技术,用于增强单元测试
20. 使用 Lambda 表达式获取 C# 中的属性名称
21. 使用 C# 的 9 个 Windows 事件日志技巧
如果你喜欢我的文章,请随意订阅
另外,请点击这些分享按钮。谢谢!
源代码
参考
文章 哪个工作更快 - Null 合并运算符、GetValueOrDefault 还是条件运算符 最先发布于 Automate The Planet。
所有图片均从 DepositPhotos.com 购买,不可免费下载和使用。
许可协议