一个方法的启动大体来讲分为两种:
- 直接运行及操作符()直接运行(直接运行)
- 通过反射方式得到对应方法的MethodInfo实例,通过Invoke()方法启动(非直接运行)
- 通过表达树方式启动(非直接运行)
往往在实现一个功能之前,我们会处理一些"前置工作",往往性能主要耗在这个阶段,而且在具体应用场景,如果没有对性能进行考量,前置工作一般不会做到统一处理或预先执行。
在这里,我们只探讨各种方式启动的性能比较,前置工作进行统一处理了。
代码如下:
class Program { static void Main(string[] args) { TestClass thisTest = new TestClass(); Stopwatch sw = new Stopwatch(); sw.Restart(); thisTest.TestMethod1(10000000); Console.WriteLine(string.Format("TestMethod1:{0}", sw.ElapsedMilliseconds)); sw.Restart(); thisTest.TestMethod2(10000000); Console.WriteLine(string.Format("TestMethod2:{0}", sw.ElapsedMilliseconds)); sw.Restart(); thisTest.TestMethod3(10000000); Console.WriteLine(string.Format("TestMethod3:{0}", sw.ElapsedMilliseconds)); sw.Stop(); Console.ReadLine(); } } public class TestClass { public void TestMethod1(int testCount) { DemoClass instance = new DemoClass(); for (int i = 0; i < testCount; i++) { instance.DemoMethod(); } } public void TestMethod2(int testCount) { DemoClass instance = new DemoClass(); Type thisType = typeof(DemoClass); MethodInfo methodInfo = thisType.GetMethod("DemoMethod"); for (int i = 0; i < testCount; i++) { methodInfo.Invoke(instance,null); } } public void TestMethod3(int testCount) { DemoClass instance = new DemoClass(); Type thisType = typeof(DemoClass); MethodInfo methodInfo = thisType.GetMethod("DemoMethod"); ParameterExpression paramExpression = Expression.Parameter(thisType, "instance"); Expression thisExpression = Expression.Call(paramExpression, methodInfo); var act = Expression.Lambda<Action<DemoClass>>(thisExpression, paramExpression).Compile(); for (int i = 0; i < testCount; i++) { act.Invoke(instance); } } } public class DemoClass { public void DemoMethod() { } }
通过测试运行1000万次我们得出结果为:
1.直接运行及操作符()直接运行 方式用时在 30至50毫秒
2.通过反射方式得到对应方法的MethodInfo实例,通过Invoke()方法启动 耗时 1300至1500毫秒
3.通过表达树方式启动 耗时 120至150毫秒
结论:在真正的应用场景当中,其实操作符运行和表达树式运行的耗时是相差微乎其微的。但如果表达树式都反射出MethodInfo然后构造表达树的情况下,表达树方式的运行性能会远远慢于操作符方式直接运行,甚至远远慢于反射方式通过Invoke()方式运行。读者可以自行测试。这里就体现了一个预处理的重要性,如果我们能够预先得到一个方法的表达树出来,并且缓存起来。每次执行时,只需要执行已经缓存好的表达树。这样看来两种方式的性能问题基本可以忽略不计。
在什么情况下会考虑非直接运行方式运行方法呢?
从根本上来讲就两方面:
1.方法的运行权限不再是程序员写死在代码当中。
2.方法的运行监控可以抽象一层出来。
从框架设计高大上来讲:
面向接口的编程方式,我们更关心的是接口功能的实现,不关心实现的具体细节。方法的调用权限的抽离,能够使得接口功能和具体实现依赖关系可以通过的特定管理机制统一控制管理。另外一方面当具体方法的调用权限通过一定机制管理,我们当然可以在调用阶段对方法的调用在一些维度上实行监控或直接控制是否可以调用。(纬度:调用前,调用后,调用方,运行时间,调用时间,调用结束时间,调用次数等)。看到这里读者应该会联想到很多流行的框架和思想了吧? 面向接口编程、面向切面编程、依赖注入、控制反转、过滤器等..