C#反射动态调用方法性能较差,有哪些替代实现方案?
反射调用性能优化方案
维护预初始化的方法列表完全可行,是这类动态调用场景性价比最高的优化方向。你当前实现的性能瓶颈根本不在方法调用本身,而在每次调用都重复执行的字符串拆分、类型查找、构造函数匹配、方法元数据解析这些操作,这些操作占了总开销的80%以上,完全可以一次性初始化后缓存复用。
方案1:预缓存调用委托(改造成本最低,收益明显)
不需要每次调用都走全流程反射,只需要在应用启动阶段一次性完成所有映射项的解析,把可直接执行的调用逻辑缓存起来,后续调用直接取缓存执行即可:
- 启动时遍历
ActionMappings字典,一次性完成类名/方法名拆分、类型查找、构造函数匹配、方法元数据获取,避免重复的元数据读取操作 - 无状态的目标类可以直接创建单例实例缓存,不需要每次调用都重新实例化
- 用表达式树或
Delegate.CreateDelegate把方法调用编译成强类型委托,委托调用的性能和原生直接调用的差距不到5%,比原生MethodInfo.Invoke快几十上百倍
参考实现代码:
// 缓存调用委托:key为传入的操作字符串,入参为object类型参数,返回值为方法执行结果 public static Dictionary<string, Func<object, object>> CachedActionDelegates = new(); // 应用启动时执行一次,完成所有映射的预解析 public static void InitializeActionMappings() { foreach (var mapping in ActionMappings) { var methodPathParts = mapping.Value.Split('.'); if (methodPathParts.Length != 2) continue; var targetType = Type.GetType($"NameSpace.{methodPathParts[0]}"); if (targetType == null) continue; // 明确匹配无参构造函数,不要用First()拿构造函数,避免顺序不确定导致的报错 var constructor = targetType.GetConstructor(Type.EmptyTypes); if (constructor == null) continue; var targetMethod = targetType.GetMethod(methodPathParts[1]); if (targetMethod == null) continue; // 提前创建无状态类的单例实例 var singletonInstance = constructor.Invoke(null); // 构建表达式树编译为强类型委托 var argsParameter = Expression.Parameter(typeof(object), "inputArgs"); var methodCallExpression = targetMethod.GetParameters().Length == 0 ? Expression.Call(Expression.Constant(singletonInstance), targetMethod) : Expression.Call( Expression.Constant(singletonInstance), targetMethod, Expression.Convert(argsParameter, targetMethod.GetParameters()[0].ParameterType) ); var returnValueConvert = Expression.Convert(methodCallExpression, typeof(object)); var compiledDelegate = Expression.Lambda<Func<object, object>>(returnValueConvert, argsParameter).Compile(); CachedActionDelegates.TryAdd(mapping.Key, compiledDelegate); } } // 运行时调用逻辑,无重复反射开销 public static object ExecuteAction(string actionName, object inputArgs) { if (!CachedActionDelegates.TryGetValue(actionName, out var action)) { throw new ArgumentException($"未找到匹配的操作:{actionName}"); } return action(inputArgs); }
如果目标类是有状态、不能复用单例的场景,只需要把实例创建逻辑放到委托内部即可,不需要提前缓存实例,性能依然远高于每次全反射实现。
方案2:源生成器实现零反射(性能最优,适配.NET 5+)
如果项目基于.NET 5及以上版本,可以用C#源生成器在编译阶段自动生成调用代码:编译期直接扫描ActionMappings配置和对应目标方法,生成硬编码的匹配调用逻辑,运行时完全不需要任何反射操作,性能和手写的硬编码分支调用完全一致,没有任何动态解析开销。
方案3:轻量缓存优化(零学习成本,快速生效)
如果不想引入表达式树、源生成器这类逻辑,只需要把每次反射拿到的Type、ConstructorInfo、MethodInfo、预创建的实例按映射key缓存起来即可,就算最终还是用MethodInfo.Invoke执行调用,性能也能比当前实现提升10倍以上——毕竟大部分反射开销都在元数据查找阶段,而非最终的Invoke执行。
注意:你当前实现中直接用
First()取构造函数存在隐患,多构造函数类型的构造函数返回顺序没有明确规范,很容易拿到非预期的构造函数导致运行时报错,建议明确匹配无参构造函数。
内容的提问来源于stack exchange,提问作者Bezhas
相关产品推荐
相关产品推荐

