C# 反射太慢!3招教你把性能提升10倍,告别CPU飙升

32 阅读8分钟

前言

在C#开发的世界里,反射机制一直是一把"双刃剑”。它赋予了我们强大的运行时能力,让框架设计变得异常灵活,无论是ORM、序列化工具,还是插件系统,都离不开它的身影。然而,这种灵活性往往伴随着性能的代价。

你是否曾遇到过这样的场景:

  • 原本毫秒级的通用对象映射操作,因为使用了反射,响应时间飙升到几百毫秒甚至更久?

  • 生产环境中,频繁的反射调用导致CPU使用率异常升高,系统响应变慢?

  • 想用反射实现一个通用组件,却被性能问题劝退,最终只能写重复代码?

这些问题的背后,正是反射性能瓶颈的真实写照。

本文将带你深入剖析反射的性能问题,并提供三种行之有效的优化方案,帮助大家在享受反射灵活性的同时,不再为性能担忧。

问题分析:反射性能瓶颈在哪里?

反射的性能问题主要集中在以下三个环节:

1、类型查找开销

每次调用 Type.GetType() 都需要在程序集中进行搜索,尤其是在大型项目中,类型查找的开销不容忽视。

2、成员信息获取

使用 GetMethod()GetProperty() 等方法获取成员信息时,需要解析大量的元数据,这个过程本身就很耗时。

3、动态调用成本

MethodInfo.Invoke() 这类动态调用方式,相比直接调用,多了参数包装、类型检查等额外开销,性能差距显著。

让我们通过一个简单的性能对比来看一下差距:

using System;
using System.Diagnostics;

namespace AppReflection
{
    publicclass PerformanceTest
    {
        public string TestMethod(string input) => input.ToUpper();
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            var obj = new PerformanceTest();
            var type = typeof(PerformanceTest);
            var method = type.GetMethod("TestMethod");

            int count = 10000000;
            string input = "hello";

            // 直接调用
            var sw1 = Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                obj.TestMethod(input);
            }
            sw1.Stop();
            Console.WriteLine($"Direct call time: {sw1.Elapsed.TotalMilliseconds} ms");

            // 反射调用
            var sw2 = Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                method.Invoke(obj, new object[] { input });
            }
            sw2.Stop();
            Console.WriteLine($"Reflection call time: {sw2.Elapsed.TotalMilliseconds} ms");
        }
    }
}

实际测试中,这两个操作的性能差距在 .NET Core 环境下可能并不明显,尤其是在调用次数较少时。.NET Core 的 JIT 编译器对反射做了大量优化,缓冲机制非常高效。甚至在某些简单场景下,.NET Framework 的反射性能反而比 .NET Core 更快。这说明,单纯的反射调用在现代 .NET 运行时中已经得到了极大改善,但复杂场景下的性能问题依然存在。

解决方案:三大性能优化技巧

技巧一:反射信息缓存机制

核心思想:一次查找,多次复用。

频繁地通过 GetType()GetMethod() 查找类型和方法信息是性能杀手。

通过将这些信息缓存起来,可以避免重复查找。

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection;

namespace AppReflection
{
    publicclass User
    {
        publicstring Name { get; set; }
        publicint Age { get; set; }
    }
    publicstaticclass ReflectionCache
    {
        privatestatic readonly ConcurrentDictionary<string, MethodInfo> _methodCache
            = new ConcurrentDictionary<string, MethodInfo>();

        privatestatic readonly ConcurrentDictionary<string, PropertyInfo> _propertyCache
            = new ConcurrentDictionary<string, PropertyInfo>();

        public static MethodInfo GetMethod(Type type, string methodName)
        {
            var key = $"{type.FullName}.{methodName}";
            return _methodCache.GetOrAdd(key, _ => type.GetMethod(methodName));
        }

        public static PropertyInfo GetProperty(Type type, string propertyName)
        {
            var key = $"{type.FullName}.{propertyName}";
            return _propertyCache.GetOrAdd(key, _ => type.GetProperty(propertyName));
        }
    }

    // 使用示例
    publicclass UserService
    {
        public void UpdateProperty(object obj, string propertyName, object value)
        {
            var property = ReflectionCache.GetProperty(obj.GetType(), propertyName);
            property.SetValue(obj, value);
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            UserService userService = new UserService();
            var user = new User { Name = "John", Age = 30 };
            var sw = Stopwatch.StartNew();
            userService.UpdateProperty(user, "Name", "Jane");
            sw.Stop();
            Console.WriteLine(sw.Elapsed.TotalMilliseconds + " ms");
        }
    }
}

适用场景

  • ORM 框架中的实体映射

  • 通用 CRUD 操作

  • 属性拷贝工具

性能提升:首次调用后,后续调用性能提升 80% 以上。

注意事项

  • 避免内存泄漏,建议使用 ConcurrentDictionary 并设置缓存过期策略或最大容量。

  • 泛型方法的缓存 Key 需要包含类型参数,避免冲突。

技巧二:委托缓存优化调用性能

核心思想:将反射调用转换为委托调用,接近原生性能。

虽然缓存了 MethodInfo,但 Invoke 调用仍然较慢。

我们可以使用 Expression 表达式树或 Delegate.CreateDelegate 将方法调用封装为委托,后续直接调用委托。

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace AppReflection
{
    publicstaticclass ReflectionCache
    {
        privatestatic readonly ConcurrentDictionary<string, MethodInfo> _methodCache
            = new ConcurrentDictionary<string, MethodInfo>();

        public static MethodInfo GetMethod(Type type, string methodName)
        {
            var key = $"{type.FullName}.{methodName}";
            return _methodCache.GetOrAdd(key, _ => type.GetMethod(methodName));
        }
    }

    publicstaticclass DelegateCache
    {
        privatestatic readonly ConcurrentDictionary<string, Func<object, object[], object>> _invokeCache
            = new ConcurrentDictionary<string, Func<object, object[], object>>();

        publicstatic Func<object, object[], object> GetInvoker(MethodInfo method)
        {
            var key = $"{method.DeclaringType.FullName}.{method.Name}";
            return _invokeCache.GetOrAdd(key, _ => CreateInvoker(method));
        }

        privatestatic Func<object, object[], object> CreateInvoker(MethodInfo method)
        {
            var instanceParam = Expression.Parameter(typeof(object), "instance");
            var parametersParam = Expression.Parameter(typeof(object[]), "parameters");

            var parameters = method.GetParameters();
            var paramExpressions = new Expression[parameters.Length];

            for (int i = 0; i < parameters.Length; i++)
            {
                var paramType = parameters[i].ParameterType;
                paramExpressions[i] = Expression.Convert(
                    Expression.ArrayIndex(parametersParam, Expression.Constant(i)),
                    paramType);
            }

            var instanceExpression = Expression.Convert(instanceParam, method.DeclaringType);
            var callExpression = Expression.Call(instanceExpression, method, paramExpressions);

            Expression resultExpression;
            if (method.ReturnType == typeof(void))
            {
                resultExpression = Expression.Block(callExpression, Expression.Constant(null));
            }
            else
            {
                resultExpression = Expression.Convert(callExpression, typeof(object));
            }

            var lambda = Expression.Lambda<Func<object, object[], object>>(
                resultExpression, instanceParam, parametersParam);

            return lambda.Compile();
        }
    }

    publicclass PerformanceTest
    {
        public string TestMethod(string input) => input.ToUpper();
    }

    publicclass ApiController
    {
        public object InvokeAction(object controller, string methodName, object[] parameters)
        {
            var method = ReflectionCache.GetMethod(controller.GetType(), methodName);
            var invoker = DelegateCache.GetInvoker(method);

            return invoker(controller, parameters);
        }
    }

    internal class Program
    {
        static async Task Main(string[] args)
        {
            var obj = new PerformanceTest();
            var apiController = new ApiController();
            var type = typeof(PerformanceTest);
            var method = type.GetMethod("TestMethod");
            int iterations = 1_000_000; // 增加到100万次,差异更明显
            string input = "hello";

            // 预热一下,防止 JIT 编译影响
            obj.TestMethod(input);
            method.Invoke(obj, new object[] { input });
            apiController.InvokeAction(obj, "TestMethod", new object[] { input });

            // 预先获取委托,避免重复查找
            var cachedInvoker = DelegateCache.GetInvoker(method);
            var parameters = new object[] { input }; // 重用参数数组

            Console.WriteLine($"开始性能测试,迭代次数: {iterations:N0}");
            Console.WriteLine(newstring('-', 50));

            // 直接调用
            var sw1 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                obj.TestMethod(input);
            }
            sw1.Stop();
            Console.WriteLine($"直接调用:     {sw1.Elapsed.TotalMilliseconds:F2} ms");

            // 反射调用
            var sw2 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                method.Invoke(obj, new object[] { input });
            }
            sw2.Stop();
            Console.WriteLine($"反射调用:     {sw2.Elapsed.TotalMilliseconds:F2} ms");

            // 委托缓存调用
            var sw3 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                apiController.InvokeAction(obj, "TestMethod", new object[] { input });
            }
            sw3.Stop();
            Console.WriteLine($"委托缓存调用: {sw3.Elapsed.TotalMilliseconds:F2} ms");

            // 优化的委托调用-直接使用缓存的委托
            var sw4 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                cachedInvoker(obj, parameters);
            }
            sw4.Stop();
            Console.WriteLine($"优化委托调用: {sw4.Elapsed.TotalMilliseconds:F2} ms");

            Console.WriteLine(newstring('-', 50));
            Console.WriteLine("性能比较(以直接调用为基准):");
            Console.WriteLine($"反射调用慢了:     {sw2.Elapsed.TotalMilliseconds / sw1.Elapsed.TotalMilliseconds:F1}x");
            Console.WriteLine($"委托缓存调用慢了: {sw3.Elapsed.TotalMilliseconds / sw1.Elapsed.TotalMilliseconds:F1}x");
            Console.WriteLine($"优化委托调用慢了: {sw4.Elapsed.TotalMilliseconds / sw1.Elapsed.TotalMilliseconds:F1}x");
        }

    }
}

适用场景

  • Web API 动态路由匹配

  • AOP 框架中的方法拦截

  • 插件系统的动态方法调用

性能提升:比直接 Invoke 快 5-10 倍,接近原生调用。

注意事项

  • 表达式树编译有一定开销,适合高频调用场景。

  • 需要处理 refout 参数和泛型方法的特殊情况。

技巧三:编译时反射代码生成

核心思想:使用 Source Generator 在编译时生成高性能代码,完全避免运行时反射。

Source Generator 是 .NET 5+ 引入的革命性特性,它允许我们在编译时分析代码并生成新的 C# 源文件。

这意味着我们可以将原本需要运行时反射完成的工作,提前到编译时完成。

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace AppReflection
{
    publicclass UserModel
    {
        publicint Id { get; set; }
        publicstring Name { get; set; }
        public DateTime CreatedAt { get; set; }
    }

    publicstaticclass UserModelReflectionHelper
    {
        public static object GetPropertyValue(UserModel obj, string propertyName)
        {
            return propertyName switch
            {
                nameof(UserModel.Id) => obj.Id,
                nameof(UserModel.Name) => obj.Name,
                nameof(UserModel.CreatedAt) => obj.CreatedAt,
                _ => thrownew ArgumentException($"Property {propertyName} not found")
            };
        }

        public static void SetPropertyValue(UserModel obj, string propertyName, object value)
        {
            switch (propertyName)
            {
                case nameof(UserModel.Id):
                    obj.Id = (int)value;
                    break;
                case nameof(UserModel.Name):
                    obj.Name = (string)value;
                    break;
                case nameof(UserModel.CreatedAt):
                    obj.CreatedAt = (DateTime)value;
                    break;
                default:
                    thrownew ArgumentException($"Property {propertyName} not found");
            }
        }
    }

    // 反射版本,用于性能对比
    publicstaticclass ReflectionPropertyHelper
    {
        privatestatic readonly ConcurrentDictionary<string, PropertyInfo> _propertyCache
            = new ConcurrentDictionary<string, PropertyInfo>();

        public static object GetPropertyValue(object obj, string propertyName)
        {
            var type = obj.GetType();
            var key = $"{type.FullName}.{propertyName}";
            var property = _propertyCache.GetOrAdd(key, _ => type.GetProperty(propertyName));
            return property?.GetValue(obj);
        }

        public static void SetPropertyValue(object obj, string propertyName, object value)
        {
            var type = obj.GetType();
            var key = $"{type.FullName}.{propertyName}";
            var property = _propertyCache.GetOrAdd(key, _ => type.GetProperty(propertyName));
            property?.SetValue(obj, value);
        }
    }

    // 使用示例
    publicclass OptimizedDataService
    {
        public void UpdateUserProperty(UserModel user, string propertyName, object value)
        {
            // 编译时生成的代码,运行时零反射开销!
            UserModelReflectionHelper.SetPropertyValue(user, propertyName, value);
        }

        public object GetUserProperty(UserModel user, string propertyName)
        {
            return UserModelReflectionHelper.GetPropertyValue(user, propertyName);
        }
    }

    internal class Program
    {
        static async Task Main(string[] args)
        {
            var optimizedDataService = new OptimizedDataService();
            var user = new UserModel { Id = 1, Name = "Alice", CreatedAt = DateTime.Now };
            int iterations = 1_000_000;

            Console.WriteLine($"性能测试开始,迭代次数: {iterations:N0}");
            Console.WriteLine(newstring('-', 60));

            // 预热
            UserModelReflectionHelper.GetPropertyValue(user, nameof(UserModel.Name));
            UserModelReflectionHelper.SetPropertyValue(user, nameof(UserModel.Name), "Warmup");
            ReflectionPropertyHelper.GetPropertyValue(user, nameof(UserModel.Name));
            ReflectionPropertyHelper.SetPropertyValue(user, nameof(UserModel.Name), "Warmup");

            // 测试 GetPropertyValue 性能
            Console.WriteLine("--- GetPropertyValue 性能对比 ---");

            // 编译时生成代码版本
            var sw1 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                var value = UserModelReflectionHelper.GetPropertyValue(user, nameof(UserModel.Name));
            }
            sw1.Stop();
            Console.WriteLine($"编译时生成代码: {sw1.Elapsed.TotalMilliseconds:F2} ms");

            // 反射版本(带缓存)
            var sw2 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                var value = ReflectionPropertyHelper.GetPropertyValue(user, nameof(UserModel.Name));
            }
            sw2.Stop();
            Console.WriteLine($"反射调用(缓存):  {sw2.Elapsed.TotalMilliseconds:F2} ms");

            // 原始反射版本(无缓存)
            var property = typeof(UserModel).GetProperty(nameof(UserModel.Name));
            var sw3 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                var value = property.GetValue(user);
            }
            sw3.Stop();
            Console.WriteLine($"原始反射调用:    {sw3.Elapsed.TotalMilliseconds:F2} ms");

            Console.WriteLine();
            Console.WriteLine("--- SetPropertyValue 性能对比 ---");

            // 编译时生成代码版本
            var sw4 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                UserModelReflectionHelper.SetPropertyValue(user, nameof(UserModel.Name), $"Name{i % 100}");
            }
            sw4.Stop();
            Console.WriteLine($"编译时生成代码: {sw4.Elapsed.TotalMilliseconds:F2} ms");

            // 反射版本(带缓存)
            var sw5 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                ReflectionPropertyHelper.SetPropertyValue(user, nameof(UserModel.Name), $"Name{i % 100}");
            }
            sw5.Stop();
            Console.WriteLine($"反射调用(缓存):  {sw5.Elapsed.TotalMilliseconds:F2} ms");

            // 原始反射版本(无缓存)
            var sw6 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                property.SetValue(user, $"Name{i % 100}");
            }
            sw6.Stop();
            Console.WriteLine($"原始反射调用:    {sw6.Elapsed.TotalMilliseconds:F2} ms");

            Console.WriteLine();
            Console.WriteLine(newstring('-', 60));
            Console.WriteLine("性能提升倍数对比:");
            Console.WriteLine($"GetPropertyValue - 编译代码 vs 反射(缓存): {sw2.Elapsed.TotalMilliseconds / sw1.Elapsed.TotalMilliseconds:F1}x 提升");
            Console.WriteLine($"GetPropertyValue - 编译代码 vs 原始反射:   {sw3.Elapsed.TotalMilliseconds / sw1.Elapsed.TotalMilliseconds:F1}x 提升");
            Console.WriteLine($"SetPropertyValue - 编译代码 vs 反射(缓存): {sw5.Elapsed.TotalMilliseconds / sw4.Elapsed.TotalMilliseconds:F1}x 提升");
            Console.WriteLine($"SetPropertyValue - 编译代码 vs 原始反射:   {sw6.Elapsed.TotalMilliseconds / sw4.Elapsed.TotalMilliseconds:F1}x 提升");

            // 测试不同属性类型的性能
            Console.WriteLine();
            Console.WriteLine("--- 不同属性类型性能测试 ---");
            TestPropertyPerformance(user, nameof(UserModel.Id), 42, iterations / 10);
            TestPropertyPerformance(user, nameof(UserModel.Name), "TestName", iterations / 10);
            TestPropertyPerformance(user, nameof(UserModel.CreatedAt), DateTime.Now, iterations / 10);
        }

        static void TestPropertyPerformance(UserModel user, string propertyName, object testValue, int iterations)
        {
            // 编译时生成代码
            var sw1 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                UserModelReflectionHelper.SetPropertyValue(user, propertyName, testValue);
                var value = UserModelReflectionHelper.GetPropertyValue(user, propertyName);
            }
            sw1.Stop();

            // 反射调用
            var sw2 = Stopwatch.StartNew();
            for (int i = 0; i < iterations; i++)
            {
                ReflectionPropertyHelper.SetPropertyValue(user, propertyName, testValue);
                var value = ReflectionPropertyHelper.GetPropertyValue(user, propertyName);
            }
            sw2.Stop();

            Console.WriteLine($"{propertyName,-12}: 编译代码 {sw1.Elapsed.TotalMilliseconds:F2}ms | 反射 {sw2.Elapsed.TotalMilliseconds:F2}ms | 提升 {sw2.Elapsed.TotalMilliseconds / sw1.Elapsed.TotalMilliseconds:F1}x");
        }
    }
}

适用场景

  • 高性能 JSON 序列化(如 System.Text.Json 的源生成器)

  • ORM 框架的实体映射代码生成

  • API 参数绑定和验证

性能提升:零运行时反射开销,等同于手写代码的性能。

注意事项

  • 需要 .NET 5+ 支持。

  • 增加了编译复杂度,适合对性能要求极高的核心组件。

总结

反射并不必然意味着性能低下。通过合理的优化策略,我们完全可以将其性能提升到可接受甚至接近原生的水平。

本文介绍的三种技巧——缓存反射信息委托化调用编译时代码生成——构成一个渐进式的优化路径:

1、初级优化:添加缓存,立竿见影。

2、中级优化:使用委托,大幅提升调用性能。

3、高级优化:引入 Source Generator,彻底摆脱运行时反射。

在实际项目中,可以根据性能需求和复杂度权衡,选择合适的方案。

记住,"缓存优先、委托转换、编译时优化” 是提升反射性能的三大核心原则。

合理运用这些技巧,让反射真正成为你开发中的利器,而不是性能的包袱。

关键词

C#、反射、性能优化、缓存、委托、Source Generator、.NET、表达式树、JIT、运行时

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!