然而,在我个人的使用中,我对这个结果产生了质疑,本着追根究底的思想,抽了个时间我做了个更详细的测试,测试主要针对有如下几种构造对象的方法:
1. 直接初始化
2. 通过ExpressionTree构造委托来初始化
3. 通过Emit构造委托初始化
4. 调用Activator.CreateInstanse(Type type)初始化
5. 调用Activator.CreateInstanse<T>()初始化
6. 使用传统反射进行初始化
在测试过程中,使用老赵的CodeTimer计时,测试代码如下:
Func<TestClass> fun = GetCreateFunc();
fun();
Func<TestClass> fun2 = GetEmitCreateFunc();
fun2();
Activator.CreateInstance(type);
Activator.CreateInstance<TestClass>();
var method = type.GetConstructors()[0];
method.Invoke(null);
GetCache.InstanceCacheEx.InstanceCache(type);
int num = 1000000;
CodeTimer.Initialize();
CodeTimer.Time("NewInstanse", num, () => {
new TestClass();
});
CodeTimer.Time("ExpressionCreate", num, () => {
fun();
});
CodeTimer.Time("EmitCreate", num, () => {
fun2();
});
CodeTimer.Time("ActivatorCreate", num, () => {
Activator.CreateInstance(type);
});
CodeTimer.Time("ReflectionCreate", num, () => {
method.Invoke(null);
});
CodeTimer.Time("GenericCreate", num, () => {
Activator.CreateInstance<TestClass>();
});
测试过程为构造100w次简单对象.测试结果如下图所示:
查看测试结果,很显然,和Will Meng的结果大不一样.在上面的测试结果中,直接初始化速度最快,ExpressionTree和Edmit初始化速度基本相同,和直接初始化相比只慢了少许 ,其次是Activator.CreateInstanse方法,让人大吃一惊的是Activator.CreateInstanse<T>方法居然是最慢的.于是,现在产生了两个疑问:
1. 为什么这份测试结果和Will Meng相差这么多
2. 为什么Activator.CreateInstanse<T>比传统反射还慢
首先看第一个疑问,对了得到最终结果,我们首先将Will Meng的测试环境重现,将这部分代码也放入我的测试项目,不过在重现过程中出现一个小问题
我对这行无法通过编译的进行了简单的修改.然后进行了测试,我将Will Meng构造的代码放入CodeTimer进行了测试,同时copy了他的测试代码也同时进行了测试,测试结果如下图(由于屏幕高度有限,这个图片分为两部分截取):
CodeTimer的结果显示,Will Men构造的关于ExpressionTree的代码仍然具有相当高的效率,但是仍比直接委托执行慢了很多,个人认为原因就在于多余的代码,比如字典,方法调用等消耗了不少时间.然而在我的系统上,完全和Will Men一样的测试代码也表现出了不同的测试结果,在我的测试环境中,Expression.CreateInstanseEx比Activator.CreateInstanse消耗的时间少2/3还多.因此,我对Will Men的测试感到实在是很疑惑,同时也希望有朋友能帮忙解答为何同样的代码,测试结果相差如此之多.
至此,第一个问题算是基本解决,在我个人的测试环境下,ExpressionTree构造对象很显然效率远高于Activator.CreateInstanse.
下面解决第二个问题,为什么Activator.CreateInstanse<T>要比Activator.CreateInstanse慢了这么多,甚至于比直接反射还要慢,要解决这个问题,我们得请出Reflector工具来查看这两个方法的具体实现,也许我们会想当然的认为Activator.CreateInstanse<T>是调用Activator.CreateInstanse方法然后转换对象为T,但是Reflector查看的结果却不是如此,微软对这两个方法进行了完全不同的实现,下面看看Activator.CreateInstanse<T>的实现:
可以看到,该方法最后调用RuntimeTypeHandle.CreateInstance方法来构造对象然后返回,接下来我们看看Activator.CreateInstanse的实现,Activator.CreateInstanse(Type type)间接调用了Activator.CreateInstanse(Type type, bool nonpublic),因此,我们直接查看该方法的实现:
很显然,这两个方法采用了不一样的方式,该方法最后调用underlyingSystemType.CreateInstanceImpl构造对象,为了得到最后答案,我们继续查看underlyingSystemType.CreateInstanceImpl方法的实现:
仍然是一个中转调用,继续查看:
终于看到了关键性实现,在这儿,该方法采用了ActivatorCache的方式进行了缓存,在未缓存的时候,会调用CreateInstanceSlow,我们继续查看CreateInstanceSlow的实现:
在这儿,我们看到了熟悉的RuntimeTypeHandle.CreateInstance来构造未缓存对象,很明显,微软也知道RuntimeTypeHandle.CreateInstance的效率并不高,因此Activator.CreateInstanse方法采用缓存的方式来提高效率,但是很是让人奇怪的是Activator.CreateInstanse<T>方法却绕过了缓存而直接调用低效率方法,这种做法实在让人不解.
现在,我们揭开了开头提出的两个疑问,在本文中,可以得到一个结论:在对象的构造中,我们优先采用直接构造的方式,在某些需要动态创建的环境下,尽可能优先采用ExpressionTree或者Emit的方式构造,如果这部分无法满足要求,可以考虑使用效率稍低的Activator.CreateInstanse方法,而效率更低的反射调用一般不在我们的考虑之列了,最后最重要一点是,一般来说,特别是对效率有要求的地方,尽可能不去使用Activator.CreateInstanse<T>方法.
补充部分:
看到Activator.CreateInstanse<T>的糟糕设计,有朋友直接质疑应该是临时工干的,因此个人又查看了.net4.0 beta2中该方法的实现,如下图:
看样子,新的framework中这个问题得到了修正.