转载:http://www.cnblogs.com/JeffreyZhao/archive/2008/11/22/invoke-method-by-lambda-expression.html
想调用一个方法很容易,直接代码调用就行,这人人都会。其次呢,还可以使用反射。不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来 看的确是这样。虽然这是个众所周知的现象,我们还是来写个程序来验证一下。比如我们现在新建一个Console应用程序,编写一个最简单的Call方法。
class Program { static void Main(string[] args) { } public void Call(object o1, object o2, object o3) { } }
Call方法接受三个object参数却没有任何实现,这样我们就可以让测试专注于方法调用,而并非方法实现本身。于是我们开始编写测试代码,比较一下方法的直接调用与反射调用的性能差距:
static void Main(string[] args) { int times = 1000000; Program program = new Program(); object[] parameters = new object[] { new object(), new object(), new object() }; program.Call(null, null, null); // force JIT-compile Stopwatch watch1 = new Stopwatch(); watch1.Start(); for (int i = 0; i < times; i++) { program.Call(parameters[0], parameters[1], parameters[2]); } watch1.Stop(); Console.WriteLine(watch1.Elapsed + " (Directly invoke)"); MethodInfo methodInfo = typeof(Program).GetMethod("Call"); Stopwatch watch2 = new Stopwatch(); watch2.Start(); for (int i = 0; i < times; i++) { methodInfo.Invoke(program, parameters); } watch2.Stop(); Console.WriteLine(watch2.Elapsed + " (Reflection invoke)"); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); }
执行结果如下:
00:00:00.0135323 (Directly invoke) 00:00:05.2325120 (Reflection invoke) Press any key to continue...
通过各调用一百万次所花时间来看,两者在性能上具有数量级的差距。因此,很多框架在必须利用到反射的场景中,都会设法使用一些较高级的替代方案 来改善性能。例如,使用CodeDom生成代码并动态编译,或者使用Emit来直接编写IL。不过自从.NET 3.5发布了Expression相关的新特性,我们在以上的情况下又有了更方便并直观的解决方案。
了解Expression相关特性的朋友可能知 道,System.Linq.Expressions.Expression<TDelegate>类型的对象在调用了它了Compile方 法之后将得到一个TDelegate类型的委托对象,而调用一个委托对象与直接调用一个方法的性能开销相差无几。那么对于上面的情况,我们又该得到什么样 的Delegate对象呢?为了使解决方案足够通用,我们必须将各种签名的方法统一至同样的委托类型中,如下:
public Func<object, object[], object> GetVoidDelegate() { Expression<Action<object, object[]>> exp = (instance, parameters) => ((Program)instance).Call(parameters[0], parameters[1], parameters[2]); Action<object, object[]> action = exp.Compile(); return (instance, parameters) => { action(action, parameters); return null; }; }
如上,我们就得到了一个Func<object, object[], object>类型的委托,这意味它接受一个object类型与object[]类型的参数,以及返回一个object类型的结果——等等,朋友们 有没有发现,这个签名与MethodInfo类型的Invoke方法完全一致?不过可喜可贺的是,我们现在调用这个委托的性能远高于通过反射来调用了。那 么对于有返回值的方法呢?那构造一个委托对象就更方便了:
public int Call(object o1, object o2) { return 0; } public Func<object, object[], object> GetDelegate() { Expression<Func<object, object[], object>> exp = (instance, parameters) => ((Program)instance).Call(parameters[0], parameters[1]); return exp.Compile(); }
至此,我想朋友们也已经能够轻松得出调用静态方法的委托构造方式了。可见,这个解决方案的关键在于构造一个合适的Expression<TDelegate>,那么我们现在就来编写一个DynamicExecuter类来作为一个较为完整的解决方案:
public class DynamicExecutor { private Func<object, object[], object> m_execute; public DynamicExecutor(MethodInfo methodInfo) { this.m_execute = this.GetExecuteDelegate(methodInfo); } public object Execute(object instance, object[] parameters) { return this.m_execute(instance, parameters); } private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo) { // parameters to execute ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance"); ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); // build parameter list List<Expression> parameterExpressions = new List<Expression>(); ParameterInfo[] paramInfos = methodInfo.GetParameters(); for (int i = 0; i < paramInfos.Length; i++) { // (Ti)parameters[i] BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType); parameterExpressions.Add(valueCast); } // non-instance for static method, or ((TInstance)instance) Expression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, methodInfo.ReflectedType); // static invoke or ((TInstance)instance).Method MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions); // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...) if (methodCall.Type == typeof(void)) { Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>( methodCall, instanceParameter, parametersParameter); Action<object, object[]> execute = lambda.Compile(); return (instance, parameters) => { execute(instance, parameters); return null; }; } else { UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object)); Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>( castMethodCall, instanceParameter, parametersParameter); return lambda.Compile(); } } }
DynamicExecutor的关键就在于GetExecuteDelegate方法中构造Expression Tree的逻辑。如果您对于一个Expression Tree的结构不太了解的话,不妨尝试一下使用Expression Tree Visualizer来 对一个现成的Expression Tree进行观察和分析。我们将一个MethodInfo对象传入DynamicExecutor的构造函数之后,就能将各组不同的实例对象和参数对象数 组传入Execute进行执行。这一切就像使用反射来进行调用一般,不过它的性能就有了明显的提高。例如我们添加更多的测试代码:
DynamicExecutor executor = new DynamicExecutor(methodInfo); Stopwatch watch3 = new Stopwatch(); watch3.Start(); for (int i = 0; i < times; i++) { executor.Execute(program, parameters); } watch3.Stop(); Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");
现在的执行结果则是:
00:00:00.0148828 (Directly invoke) 00:00:05.2488540 (Reflection invoke) 00:00:00.0618695 (Dynamic executor) Press any key to continue...
事实上,Expression<TDelegate>类型的Compile方法正是使用Emit来生成委托对象。不过现在我们已经 无需将目光放在更低端的IL上,只要使用高端的API来进行Expression Tree的构造,这无疑是一种进步。不过这种方法也有一定局限性,例如我们只能对公有方法进行调用,并且除了方法外的其他类型成员,我们就无法如上例般惬 意地编写代码了。