程序集间高效动态委托调用优化方案咨询
高效调用委托的优化方案(替代DynamicInvoke)
针对你提出的「多次调用同一委托、参数匹配固定、允许首次开销、需降低动态调用损耗」的场景,最优方案是将动态委托转换为强类型委托并缓存编译结果,完全规避DynamicInvoke的动态校验与装箱开销。以下是两种落地实现:
方案1:泛型强类型委托(性能最优)
直接提供泛型版本的调用方法,让调用方传入强类型委托,完全消除动态特性开销,性能接近直接方法调用。
修改后的Invoker类库代码
namespace InvokeLibrary { public static class Invoke { // 单参数泛型调用方法 public static void Invoker<T>(Action<T> method, T data) { for (int i = 0; i < 1000000; i++) method(data); } // 可扩展多参数版本,比如双参数 public static void Invoker<T1, T2>(Action<T1, T2> method, T1 data1, T2 data2) { for (int i = 0; i < 1000000; i++) method(data1, data2); } } }
调用方代码调整
using InvokeLibrary; var obj = new A("Message from caller"); // 直接传入强类型Action<A>委托 Invoke.Invoker<A>(ToBeInvoked, obj); void ToBeInvoked(A obj) => Console.WriteLine("A: " + obj.Message); record A(string Message);
这种方式没有任何装箱拆箱或动态校验,是性能最高的实现,推荐在调用方能提供强类型委托时优先使用。
方案2:表达式树编译+缓存(兼容原有Delegate入参)
如果必须保留原有的Delegate+object[]入参形式,可以通过表达式树将动态委托编译为强类型委托并缓存,首次编译有一次性开销,后续调用性能接近强类型调用。
修改后的Invoker类库代码
namespace InvokeLibrary { public static class Invoke { // 缓存编译后的强类型委托,避免重复编译 private static readonly ConcurrentDictionary<Delegate, Delegate> _compiledCache = new(); public static void Invoker(Delegate method, params object[] data) { if (_compiledCache.TryGetValue(method, out var compiledDelegate)) { // 根据参数数量分发调用(示例仅处理单参数场景,可扩展多参数) ((Action<object>)compiledDelegate)(data[0]); return; } // 构建表达式树:将object参数强转为目标类型,调用原方法 var methodParams = method.Method.GetParameters(); var paramExpr = Expression.Parameter(typeof(object)); var castExpr = Expression.Convert(paramExpr, methodParams[0].ParameterType); var callExpr = Expression.Call(Expression.Constant(method.Target), method.Method, castExpr); var lambdaExpr = Expression.Lambda<Action<object>>(callExpr, paramExpr); // 编译并缓存委托 var compiled = lambdaExpr.Compile(); _compiledCache.TryAdd(method, compiled); compiled(data[0]); } } }
原理说明
- 首次调用时,通过表达式树生成强类型的
Action<object>委托,自动完成参数的类型转换; - 用
ConcurrentDictionary缓存编译结果,同一委托只会编译一次; - 后续调用直接执行编译后的强类型委托,完全避免
DynamicInvoke的动态校验、装箱拆箱开销。
性能对比
三种方式的性能排序为:
泛型强类型调用 > 表达式树缓存调用 > Delegate.DynamicInvoke/MethodInfo.Invoke
表达式树缓存方案的首次调用开销约为DynamicInvoke的数倍,但后续百万次调用的性能提升可达10~100倍(具体取决于参数类型与方法逻辑)。
内容的提问来源于stack exchange,提问作者Yoav Haik
相关产品推荐
相关产品推荐

