编辑
2026-02-17
C#
00

目录

🚀 C#空值检查的3种方法:性能差距竟然这么大!
🔍 问题分析:为什么空值检查会影响性能?
💥 常见的三大痛点
💡 三种解决方案详解
🥇 方法一:传统的 == null 检查
🥈 方法二:EqualityComparer 检查(推荐)
🥉 方法三:现代的 is null 检查
完整代码
🎯 最佳实践建议
📋 选择指南
⚡ 性能优化技巧
🏆 总结与收获
🎯 三大核心要点

🚀 C#空值检查的3种方法:性能差距竟然这么大!

你是否在C#开发中遇到过这样的困惑:明明都是检查null值,为什么有时候程序卡顿,有时候内存飙升?最近在优化一个高并发项目时,我发现了一个惊人的事实:不同的空值检查方式,性能上确实有差距!

今天就来深度剖析C#中三种常见的空值检查方法,通过实战测试告诉你:哪种方法最快、最省内存,以及什么时候该用哪种方法。相信看完这篇文章,你的代码性能将得到质的提升!

🔍 问题分析:为什么空值检查会影响性能?

在日常开发中,我们经常需要进行空值检查,特别是在处理用户输入、API响应、数据库查询结果等场景。然而,很多开发者并不知道,不同的空值检查方式会带来截然不同的性能表现。

💥 常见的三大痛点

  1. 装箱问题:值类型使== null时会发生装箱,导致额外的内存分配
  2. GC压力:频繁的装箱操作会触发垃圾回收,影响程序响应速度
  3. 类型限制:某些检查方法只能用于特定类型,缺乏通用性

💡 三种解决方案详解

🥇 方法一:传统的 == null 检查

这是最常见但也是最容易踩坑的方法:

c#
public static bool IsNullMethod1<T>(T value) { return value == null; } // 使用示例 int number = 42; bool result = IsNullMethod1(number); // ❌ 会导致装箱!

⚠️ 关键坑点:

  • 对值类型使用时会发生装箱,创建临时对象
  • 装箱操作会在堆上分配内存,增加GC压力
  • 在高频调用场景下性能损失明显

🥈 方法二:EqualityComparer 检查(推荐)

这是性能最佳的通用解决方案:

c#
namespace AppCheckNull { internal class Program { static void Main(string[] args) { // 使用示例 int number = 42; string text = "hello"; DateTime date = DateTime.Now; bool result1 = IsNullMethod2(number); // 无装箱,性能最佳 bool result2 = IsNullMethod2(text); // 适用于引用类型 bool result3 = IsNullMethod2(date); // 适用于任何类型 } public static bool IsNullMethod2<T>(T value) { return EqualityComparer<T>.Default.Equals(value, default(T)); } } }

✨ 核心优势:

  • 零装箱:对值类型不会产生装箱操作
  • 通用性强:适用于所有类型
  • 性能最佳:测试显示比方法一快80%

🥉 方法三:现代的 is null 检查

C# 7.0引入的模式匹配语法:

c#
namespace AppCheckNull { internal class Program { static void Main(string[] args) { // 使用示例 string text = "hello"; int? nullableNumber = null; bool result1 = IsNullMethod3(text); // 引用类型 bool result2 = IsNullMethod3Nullable(nullableNumber); // 可空值类型 Console.WriteLine($"Is text null? {result1}"); Console.WriteLine($"Is nullableNumber null? {result2}"); } // 引用类型版本 public static bool IsNullMethod3<T>(T value) where T : class { return value is null; } // 可空值类型版本 public static bool IsNullMethod3Nullable<T>(T? value) where T : struct { return value is null; } } }

image.png

🎯 适用场景:

  • 引用类型的空值检查
  • 可空值类型的空值检查
  • 代码可读性要求较高的场景

完整代码

c#
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace AppNullGc { class Program { // 测试数据规模 private const int TestIterations = 1000000; static void Main(string[] args) { Console.WriteLine("=== C# 空值检查性能测试 ===\n"); // 测试不同的数据类型 RunPerformanceTests(); // 内存分配测试 RunMemoryAllocationTest(); // GC压力测试 RunGCPressureTest(); Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } #region 三种空值检查方法的实现 /// <summary> /// 方法1:使用 == null (会导致值类型装箱) /// </summary> public static bool IsNullMethod1<T>(T value) { return value == null; } /// <summary> /// 方法2:使用 EqualityComparer (推荐方法,无装箱) /// </summary> public static bool IsNullMethod2<T>(T value) { return EqualityComparer<T>.Default.Equals(value, default(T)); } /// <summary> /// 方法3:使用 is null (仅适用于引用类型和可空值类型) /// </summary> public static bool IsNullMethod3<T>(T value) where T : class { return value is null; } /// <summary> /// 方法3的可空值类型版本 /// </summary> public static bool IsNullMethod3Nullable<T>(T? value) where T : struct { return value is null; } #endregion #region 性能测试方法 static void RunPerformanceTests() { Console.WriteLine("1. 值类型性能测试 (int)"); TestValueType<int>(42); Console.WriteLine(); Console.WriteLine("2. 值类型性能测试 (bool)"); TestValueType<bool>(true); Console.WriteLine(); Console.WriteLine("3. 值类型性能测试 (DateTime)"); TestValueType<DateTime>(DateTime.Now); Console.WriteLine(); Console.WriteLine("4. 引用类型性能测试 (string)"); TestReferenceType<string>("test"); Console.WriteLine(); Console.WriteLine("5. 引用类型性能测试 (object)"); TestReferenceType<object>(new object()); Console.WriteLine(); Console.WriteLine("6. 可空值类型性能测试 (int?)"); TestNullableValueType<int>(42); Console.WriteLine(); } static void TestValueType<T>(T testValue) where T : struct { var values = GenerateValueTypeArray<T>(testValue); Console.WriteLine($"测试数组大小: {values.Length:N0} 元素"); Console.WriteLine($"测试类型: {typeof(T).Name}"); // 记录GC计数 var gc0Before = GC.CollectionCount(0); var gc1Before = GC.CollectionCount(1); var gc2Before = GC.CollectionCount(2); // 方法1:== null (装箱) var sw1 = Stopwatch.StartNew(); int count1 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod1(value)) count1++; } } sw1.Stop(); var gc0After1 = GC.CollectionCount(0); var gc1After1 = GC.CollectionCount(1); var gc2After1 = GC.CollectionCount(2); // 方法2:EqualityComparer var sw2 = Stopwatch.StartNew(); int count2 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod2(value)) count2++; } } sw2.Stop(); var gc0After2 = GC.CollectionCount(0); var gc1After2 = GC.CollectionCount(1); var gc2After2 = GC.CollectionCount(2); Console.WriteLine($"方法1 (== null): {sw1.ElapsedMilliseconds:N0} ms, {sw1.ElapsedTicks:N0} ticks"); Console.WriteLine($" GC Gen0: {gc0After1 - gc0Before}, Gen1: {gc1After1 - gc1Before}, Gen2: {gc2After1 - gc2Before}"); Console.WriteLine($"方法2 (EqualityComparer): {sw2.ElapsedMilliseconds:N0} ms, {sw2.ElapsedTicks:N0} ticks"); Console.WriteLine($" GC Gen0: {gc0After2 - gc0After1}, Gen1: {gc1After2 - gc1After1}, Gen2: {gc2After2 - gc2After1}"); if (sw1.ElapsedTicks > 0) { var improvement = ((double)(sw1.ElapsedTicks - sw2.ElapsedTicks) / sw1.ElapsedTicks) * 100; Console.WriteLine($"性能提升: {improvement:F1}%"); } } static void TestReferenceType<T>(T testValue) where T : class { var values = GenerateReferenceTypeArray<T>(testValue); Console.WriteLine($"测试数组大小: {values.Length:N0} 元素"); Console.WriteLine($"测试类型: {typeof(T).Name}"); // 方法1:== null var sw1 = Stopwatch.StartNew(); int count1 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod1(value)) count1++; } } sw1.Stop(); // 方法2:EqualityComparer var sw2 = Stopwatch.StartNew(); int count2 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod2(value)) count2++; } } sw2.Stop(); // 方法3:is null var sw3 = Stopwatch.StartNew(); int count3 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod3(value)) count3++; } } sw3.Stop(); Console.WriteLine($"方法1 (== null): {sw1.ElapsedMilliseconds:N0} ms, {sw1.ElapsedTicks:N0} ticks"); Console.WriteLine($"方法2 (EqualityComparer): {sw2.ElapsedMilliseconds:N0} ms, {sw2.ElapsedTicks:N0} ticks"); Console.WriteLine($"方法3 (is null): {sw3.ElapsedMilliseconds:N0} ms, {sw3.ElapsedTicks:N0} ticks"); if (sw1.ElapsedTicks > 0) { var improvement2 = ((double)(sw1.ElapsedTicks - sw2.ElapsedTicks) / sw1.ElapsedTicks) * 100; var improvement3 = ((double)(sw1.ElapsedTicks - sw3.ElapsedTicks) / sw1.ElapsedTicks) * 100; Console.WriteLine($"EqualityComparer 性能提升: {improvement2:F1}%"); Console.WriteLine($"is null 性能提升: {improvement3:F1}%"); } } static void TestNullableValueType<T>(T testValue) where T : struct { var values = GenerateNullableValueTypeArray<T>(testValue); Console.WriteLine($"测试数组大小: {values.Length:N0} 元素"); Console.WriteLine($"测试类型: {typeof(T).Name}?"); // 方法1:== null var sw1 = Stopwatch.StartNew(); int count1 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod1(value)) count1++; } } sw1.Stop(); // 方法2:EqualityComparer var sw2 = Stopwatch.StartNew(); int count2 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod2(value)) count2++; } } sw2.Stop(); // 方法3:is null (可空值类型版本) var sw3 = Stopwatch.StartNew(); int count3 = 0; for (int i = 0; i < TestIterations; i++) { foreach (var value in values) { if (IsNullMethod3Nullable(value)) count3++; } } sw3.Stop(); Console.WriteLine($"方法1 (== null): {sw1.ElapsedMilliseconds:N0} ms, {sw1.ElapsedTicks:N0} ticks"); Console.WriteLine($"方法2 (EqualityComparer): {sw2.ElapsedMilliseconds:N0} ms, {sw2.ElapsedTicks:N0} ticks"); Console.WriteLine($"方法3 (is null): {sw3.ElapsedMilliseconds:N0} ms, {sw3.ElapsedTicks:N0} ticks"); if (sw1.ElapsedTicks > 0) { var improvement2 = ((double)(sw1.ElapsedTicks - sw2.ElapsedTicks) / sw1.ElapsedTicks) * 100; var improvement3 = ((double)(sw1.ElapsedTicks - sw3.ElapsedTicks) / sw1.ElapsedTicks) * 100; Console.WriteLine($"EqualityComparer 性能提升: {improvement2:F1}%"); Console.WriteLine($"is null 性能提升: {improvement3:F1}%"); } } #endregion #region 内存分配测试 static void RunMemoryAllocationTest() { Console.WriteLine("=== 内存分配测试 ==="); Console.WriteLine("测试装箱操作对GC的影响\n"); const int iterations = 100000; var intValues = Enumerable.Range(0, 100).ToArray(); // 强制垃圾回收,获取基准内存 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); long memoryBefore = GC.GetTotalMemory(false); // 测试方法1(装箱) var sw1 = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { foreach (var value in intValues) { IsNullMethod1(value); // 会导致装箱 } } sw1.Stop(); long memoryAfter1 = GC.GetTotalMemory(false); // 强制垃圾回收,重置内存 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); long memoryBefore2 = GC.GetTotalMemory(false); // 测试方法2(无装箱) var sw2 = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { foreach (var value in intValues) { IsNullMethod2(value); // 无装箱 } } sw2.Stop(); long memoryAfter2 = GC.GetTotalMemory(false); Console.WriteLine($"方法1 (== null) - 装箱方法:"); Console.WriteLine($" 执行时间: {sw1.ElapsedMilliseconds} ms"); Console.WriteLine($" 内存分配: {(memoryAfter1 - memoryBefore):N0} bytes"); Console.WriteLine(); Console.WriteLine($"方法2 (EqualityComparer) - 无装箱方法:"); Console.WriteLine($" 执行时间: {sw2.ElapsedMilliseconds} ms"); Console.WriteLine($" 内存分配: {(memoryAfter2 - memoryBefore2):N0} bytes"); Console.WriteLine(); if (sw1.ElapsedMilliseconds > 0) { var speedImprovement = ((double)(sw1.ElapsedMilliseconds - sw2.ElapsedMilliseconds) / sw1.ElapsedMilliseconds) * 100; Console.WriteLine($"速度提升: {speedImprovement:F1}%"); } long memoryDifference = (memoryAfter1 - memoryBefore) - (memoryAfter2 - memoryBefore2); Console.WriteLine($"内存节省: {memoryDifference:N0} bytes"); } static void RunGCPressureTest() { Console.WriteLine("\n=== GC 压力测试 ==="); Console.WriteLine("模拟高频null检查对GC的影响\n"); const int iterations = 50000; var testData = Enumerable.Range(0, 1000).ToArray(); // 测试装箱方法的GC压力 var gcBefore = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; var memoryBefore = GC.GetTotalMemory(false); var sw1 = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { foreach (var item in testData) { IsNullMethod1(item); // 装箱操作 IsNullMethod1(DateTime.Now); // 装箱操作 IsNullMethod1(i % 2 == 0); // 装箱操作 } } sw1.Stop(); var gcAfter1 = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; var memoryAfter1 = GC.GetTotalMemory(false); // 清理内存 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // 测试无装箱方法的GC压力 var gcBefore2 = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; var memoryBefore2 = GC.GetTotalMemory(false); var sw2 = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { foreach (var item in testData) { IsNullMethod2(item); // 无装箱操作 IsNullMethod2(DateTime.Now); // 无装箱操作 IsNullMethod2(i % 2 == 0); // 无装箱操作 } } sw2.Stop(); var gcAfter2 = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; var memoryAfter2 = GC.GetTotalMemory(false); Console.WriteLine("装箱方法 (== null):"); Console.WriteLine($" 执行时间: {sw1.ElapsedMilliseconds} ms"); Console.WriteLine($" 内存峰值: {(memoryAfter1 - memoryBefore):N0} bytes"); Console.WriteLine($" GC次数 - Gen0: {gcAfter1[0] - gcBefore[0]}, Gen1: {gcAfter1[1] - gcBefore[1]}, Gen2: {gcAfter1[2] - gcBefore[2]}"); Console.WriteLine("\n无装箱方法 (EqualityComparer):"); Console.WriteLine($" 执行时间: {sw2.ElapsedMilliseconds} ms"); Console.WriteLine($" 内存峰值: {(memoryAfter2 - memoryBefore2):N0} bytes"); Console.WriteLine($" GC次数 - Gen0: {gcAfter2[0] - gcBefore2[0]}, Gen1: {gcAfter2[1] - gcBefore2[1]}, Gen2: {gcAfter2[2] - gcBefore2[2]}"); Console.WriteLine($"\nGC减少次数 - Gen0: {(gcAfter1[0] - gcBefore[0]) - (gcAfter2[0] - gcBefore2[0])}, " + $"Gen1: {(gcAfter1[1] - gcBefore[1]) - (gcAfter2[1] - gcBefore2[1])}, " + $"Gen2: {(gcAfter1[2] - gcBefore[2]) - (gcAfter2[2] - gcBefore2[2])}"); } #endregion #region 测试数据生成器 static T[] GenerateValueTypeArray<T>(T sampleValue) where T : struct { var array = new T[1000]; for (int i = 0; i < array.Length; i++) { array[i] = i % 2 == 0 ? sampleValue : default(T); } return array; } static T[] GenerateReferenceTypeArray<T>(T sampleValue) where T : class { var array = new T[1000]; for (int i = 0; i < array.Length; i++) { array[i] = i % 2 == 0 ? sampleValue : null; } return array; } static T?[] GenerateNullableValueTypeArray<T>(T sampleValue) where T : struct { var array = new T?[1000]; for (int i = 0; i < array.Length; i++) { array[i] = i % 3 == 0 ? sampleValue : (i % 3 == 1 ? null : default(T)); } return array; } #endregion } #region 实际应用场景示例 /// <summary> /// 通用空值检查工具类(推荐使用) /// </summary> public static class NullChecker { /// <summary> /// 通用空值检查(推荐 - 适用于所有类型,无装箱) /// </summary> public static bool IsNull<T>(T value) { return EqualityComparer<T>.Default.Equals(value, default(T)); } /// <summary> /// 引用类型专用空值检查 /// </summary> public static bool IsNullReference<T>(T value) where T : class { return value is null; } /// <summary> /// 可空值类型专用空值检查 /// </summary> public static bool IsNullNullable<T>(T? value) where T : struct { return value is null; } } #endregion }

image.png

🎯 最佳实践建议

📋 选择指南

场景推荐方法原因
通用场景`EqualityComparer`性能最佳,适用性最广
引用类型`is null`代码更清晰,无性能损失
可空值类型`is null`类型安全,可读性好
高频调用`EqualityComparer`零GC压力,内存友好

⚡ 性能优化技巧

避免在循环中使用装箱检查

c#
// ❌ 错误示例 - 每次都装箱 foreach (var item in numbers) { if (item == null) continue; // 装箱! } // ✅ 正确示例 - 零装箱 foreach (var item in numbers) { if (NullChecker.IsNull(item)) continue; // 高性能! }

缓存EqualityComparer实例(进阶优化)

c#
public static class OptimizedNullChecker<T> { private static readonly EqualityComparer<T> Comparer = EqualityComparer<T>.Default; private static readonly T DefaultValue = default(T); public static bool IsNull(T value) { return Comparer.Equals(value, DefaultValue); } }

🏆 总结与收获

通过深入分析和实战测试,我们得出了以下关键结论:

🎯 三大核心要点

  1. 性能为王EqualityComparer方法在高频场景下比传统方法快5倍以上
  2. 内存友好:避免装箱操作能显著减少GC压力,提升应用响应速度
  3. 因地制宜:不同场景选择不同方法,既要考虑性能也要兼顾代码可读性

🤝 互动时间

你在项目中遇到过哪些性能瓶颈?在空值检查方面有什么经验分享?欢迎在评论区聊聊你的看法!

如果这篇文章对你有帮助,别忘了转发给更多需要的同行。让我们一起写出更高效的C#代码!

关注我,获取更多C#性能优化干货!

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!