反射调用 MethodInfo.Invoke
是高并发下的主要性能瓶颈
在 Web API 或高频服务中,直接用
MethodInfo.Invoke调用方法,会触发完整的反射解析流程:类型检查、参数绑定、访问权限校验、装箱/拆箱(值类型)、异常包装。单次调用开销约是直接调用的 50–100 倍;并发量上来后,GC 压力和 CPU 竞争会急剧放大。 每次
Invoke都新建
object[]参数数组,引发堆分配 泛型方法或带
ref/
out参数时,绑定逻辑更重,容易抛
TargetParameterCountException.NET 6+ 中 JIT 对反射调用的内联完全失效,无法通过 AOT 缓解
用 Delegate.CreateDelegate
替代 Invoke
可提速 10–30 倍
将反射获取的
MethodInfo编译为强类型委托,后续调用就等同于原生方法调用。关键在于:委托只创建一次,复用在所有请求中。
var method = typeof(MyService).GetMethod("Process");
// 推荐:指定委托类型,避免 object[] 拆包
var func = (Func<MyService, int, string>)Delegate.CreateDelegate(
typeof(Func<MyService, int, string>),
null,
method);
// 后续直接调用,无反射开销
var result = func(instance, 42);
必须确保 method是静态或实例方法,并与委托签名严格匹配(含
this参数位置) 不能用于泛型方法的开放构造(如
List<t>.Add</t>),需先用
MakeGenericMethod闭合 委托缓存要线程安全:建议用
Lazy<t></t>或
ConcurrentDictionary存储
Expression.Lambda
编译动态委托适合复杂绑定场景
当参数需运行时映射(如从
IDictionary<string object></string>构建调用参数)、或涉及属性访问/转换时,
Expression比
CreateDelegate更灵活,且编译后性能几乎无损。
var instanceParam = Expression.Parameter(typeof(object), "instance");
var argParam = Expression.Parameter(typeof(object), "arg");
var convertedArg = Expression.Convert(argParam, typeof(int));
var call = Expression.Call(
Expression.Convert(instanceParam, typeof(MyService)),
typeof(MyService).GetMethod("Process"),
convertedArg
);
var lambda = Expression.Lambda<Func<object, object, string>>(call, instanceParam, argParam);
var compiled = lambda.Compile(); // 一次性编译
// 复用 compiled
var result = compiled(serviceObj, 42);
首次编译耗时较高(毫秒级),但之后调用接近直接调用
注意表达式树中类型转换错误会导致 InvalidOperationException,应在初始化阶段验证 避免在热路径里反复调用
Compile(),它不可缓存且触发 JIT
真正需要避免的是「每次请求都反射」
很多框架(如早期 ASP.NET Core Model Binding)会在每次请求中重新获取
PropertyInfo、调用
GetValue,这比方法调用更糟——属性访问还涉及索引器、
get_方法查找、
BindingFlags过滤等额外步骤。 把
PropertyInfo和对应 getter/setter 委托缓存在静态字典里,键可为
(Type, PropertyName)用
System.Reflection.Metadata+
MetadataReader在启动时预扫描类型,生成轻量元数据缓存,绕过运行时反射 API 对核心高频对象(如 DTO),直接手写映射代码或用 Source Generator 生成,彻底消灭运行时反射
反射不是不能用,而是不能“裸用”。高并发下,任何未缓存、未编译、未静态化的反射操作,都会成为吞吐量的隐形天花板。
