你是否在C#开发中遇到过这样的困惑:明明都是检查null值,为什么有时候程序卡顿,有时候内存飙升?最近在优化一个高并发项目时,我发现了一个惊人的事实:不同的空值检查方式,性能上确实有差距!
今天就来深度剖析C#中三种常见的空值检查方法,通过实战测试告诉你:哪种方法最快、最省内存,以及什么时候该用哪种方法。相信看完这篇文章,你的代码性能将得到质的提升!
在日常开发中,我们经常需要进行空值检查,特别是在处理用户输入、API响应、数据库查询结果等场景。然而,很多开发者并不知道,不同的空值检查方式会带来截然不同的性能表现。
这是最常见但也是最容易踩坑的方法:
c#public static bool IsNullMethod1<T>(T value)
{
return value == null;
}
// 使用示例
int number = 42;
bool result = IsNullMethod1(number); // ❌ 会导致装箱!
⚠️ 关键坑点:
这是性能最佳的通用解决方案:
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));
}
}
}
✨ 核心优势:
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;
}
}
}

🎯 适用场景:
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
}

| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 通用场景 | `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);
}
}
通过深入分析和实战测试,我们得出了以下关键结论:
EqualityComparer方法在高频场景下比传统方法快5倍以上🤝 互动时间
你在项目中遇到过哪些性能瓶颈?在空值检查方面有什么经验分享?欢迎在评论区聊聊你的看法!
如果这篇文章对你有帮助,别忘了转发给更多需要的同行。让我们一起写出更高效的C#代码!
关注我,获取更多C#性能优化干货!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!