trueideal

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

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

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

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

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

💥 常见的三大痛点

💡 三种解决方案详解

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

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

public static bool IsNullMethod1<T>(T value)
{
    return value == null;
}

// 使用示例
int number = 42;
bool result = IsNullMethod1(number); // ❌ 会导致装箱!

⚠️ 关键坑点:

  • • 对值类型使用时会发生装箱,创建临时对象

  • • 装箱操作会在堆上分配内存,增加GC压力

  • • 在高频调用场景下性能损失明显

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

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

namespace AppCheckNull
{

    internalclassProgram
    {
        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引入的模式匹配语法:

namespace AppCheckNull
{

    internalclassProgram
    {
        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
        {
            returnvalueisnull;
        }

        // 可空值类型版本
        public static bool IsNullMethod3Nullable<T>(T? value) where T : struct
        {
            returnvalueisnull;
        }
    }
}

Image

🎯 适用场景:

  • • 引用类型的空值检查

  • • 可空值类型的空值检查

  • • 代码可读性要求较高的场景

完整代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespaceAppNullGc
{
    classProgram
    {
        // 测试数据规模
        privateconstint 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)
        {
            returnvalue == 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
        {
            returnvalueisnull;
        }

        /// <summary>
        /// 方法3的可空值类型版本
        /// </summary>
        public static bool IsNullMethod3Nullable<T>(T? value) where T : struct
        {
            returnvalueisnull;
        }

        #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>(newobject());
            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 (varvaluein 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 (varvaluein 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 (varvaluein values)
                {
                    if (IsNullMethod1(value)) count1++;
                }
            }
            sw1.Stop();

            // 方法2:EqualityComparer
            var sw2 = Stopwatch.StartNew();
            int count2 = 0;
            for (int i = 0; i < TestIterations; i++)
            {
                foreach (varvaluein 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 (varvaluein 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 (varvaluein values)
                {
                    if (IsNullMethod1(value)) count1++;
                }
            }
            sw1.Stop();

            // 方法2:EqualityComparer
            var sw2 = Stopwatch.StartNew();
            int count2 = 0;
            for (int i = 0; i < TestIterations; i++)
            {
                foreach (varvaluein 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 (varvaluein 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");

            constint 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 (varvaluein 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 (varvaluein 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");

            constint iterations = 50000;
            var testData = Enumerable.Range(0, 1000).ToArray();

            // 测试装箱方法的GC压力
            var gcBefore = newint[] { 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 = newint[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) };
            var memoryAfter1 = GC.GetTotalMemory(false);

            // 清理内存
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            // 测试无装箱方法的GC压力
            var gcBefore2 = newint[] { 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 = newint[] { 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>
    publicstaticclassNullChecker
    {
        /// <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
        {
            returnvalueisnull;
        }

        /// <summary>
        /// 可空值类型专用空值检查
        /// </summary>
        public static bool IsNullNullable<T>(T? value) where T : struct
        {
            returnvalueisnull;
        }
    }

    #endregion
}

Image

🎯 最佳实践建议

📋 选择指南

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

⚡ 性能优化技巧

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

// ❌ 错误示例 - 每次都装箱
foreach (var item in numbers)
{
    if (item == null) continue; // 装箱!
}

// ✅ 正确示例 - 零装箱
foreach (var item in numbers)
{
    if (NullChecker.IsNull(item)) continue; // 高性能!
}

缓存EqualityComparer实例(进阶优化)

public staticclassOptimizedNullChecker<T>
{
    privatestaticreadonly EqualityComparer<T> Comparer = EqualityComparer<T>.Default;
    privatestaticreadonly T DefaultValue = default(T);
    
    public static bool IsNull(T value)
    {
        return Comparer.Equals(value, DefaultValue);
    }
}

🏆 总结与收获

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

🎯 三大核心要点


🤝 互动时间

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

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

关注我,获取更多C[#性能优化干货](javascript:😉!

posted on 2026-02-18 07:13  trueideal  阅读(1)  评论(0)    收藏  举报

导航