反射我该用什么?——TypeDescriptor家族和Type家族大比拼
可能有所经验的老鸟都知道,反射有两种用法:使用TypeDescriptor(包括PropertyDescriptor等)或者Type(包括PropertyInfo等MemberInfo)。但是我相信绝大多数童鞋们都很疑惑,微软为什么要整出两种反射呢?也不是是很清楚,这两种反射的区别在哪,在什么情况下应该用哪种反射,它们各自的性能如何?
其实玄机就在它们的namespace里,Type家族的namespace是System.Reflection(不包括Type类型本身); 而TypeDescriptor家族的namespace却是System.ComponentModel。System.Reflection这个namespace里面的所有类型都是为反射服务的;而System.ComponentModel却是Component的核心namespace,表明TypeDescriptor和Component关系紧密。
Type是静态的基于类型反射,一旦给定一个Type,那么用Type家族就可以获取这个Type的所有信息以及这个Type上所有的成员,无论这个成员是否是Public的。所以使用Type家族能够满足运行时的所有反射的需求。
而对于TypeDescriptor,我们可以望名思意:TypeDescriptor就是对于Type的描述,而且这个描述不是固定的,它会变化和更改,从而使得它与原来的Type不一样,TypeDescriptor可以理解为对Type的包装。TypeDescriptor是为了处理设计时的一些需求而存在的,因为在设计时会有动态改变类型、成员和Attribute等的需求;而在运行时这种需求却很几乎没有。TypeDescriptor为了能够动态的改变类型、成员和Attribute等,必定会在内存中有所缓存,类型的缓存存在于一个静态的WeakHashtable中。而成员、Attribute等的缓存机制则更加复杂。而且TypeDescriptor只能处理Public的成员。
根据上面的介绍,我们可以做出一个推测:TypeDescriptor由于要建立缓存机制并包装Type,所以TypeDescriptor得性能应该会比Type慢;但是缓存建好后,再重复使用TypeDescriptor的性能会有所提升。
以下写了一个简单的测试程序测试获取所有属性的性能:
class Program
{
static void Main(string[] args)
{
int count = 1000000;
TestTypeDescriptorGetProperties(typeof(Component), count);
TestTypeGetProperties(typeof(Component), count);
TestTypeDescriptorGetProperties(typeof(Control), count);
TestTypeGetProperties(typeof(Control), count);
}
private static void TestTypeDescriptorGetProperties(Type type, params int[] counts)
{
foreach (int count in counts)
{
object obj = Activator.CreateInstance(type);
Stopwatch watch = Stopwatch.StartNew();
Console.Write("TypeDescriptor.GetProperties(" + type.Name + " 类型 ) " + count + "次: ");
for (int i = 0; i < count; i++)
{
TypeDescriptor.GetProperties(type);
}
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
}
}
private static void TestTypeGetProperties(Type type, params int[] counts)
{
foreach (int count in counts)
{
object obj = Activator.CreateInstance(type);
Stopwatch watch = Stopwatch.StartNew();
Console.Write("type.GetProperties(" + type.Name + " 类型 ) " + count + "次: ");
for (int i = 0; i < count; i++)
{
type.GetProperties();
}
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
}
}
}
在我本机改变几次Count然后运行的结果如下:
通过对结果的分析,我们可以得到这样的结论:
TypeDescriptor第一次使用的性能很差;基本随着使用次数的增加而成线性变化;目标类型的成员数量对性能的影响不大。Type也基本随着使用次数的增加而成线性变化,但是目标类型成员的数量与性能也成线性变化,获取Control上面所有属性的性能大概是获取Component上所有属性性能的10几倍。当然,在一般的情况下,我们对于反射的使用不会达到这个数量级,因为我们可以在程序中增加缓存机制以避免重复的读取反射,所以一般情况下我们对于反射的性恩那个不必要太关注。
综上所述,我认为如果我们在运行时使用反射,最好使用Type,因为一般而言,Type的性能比较高而且通过Type能够访问非Public的成员。但是由于TypeDescriptor有着更加友好的接口,使用起来更加方便,而且使用TypeDescriptor的时候一般情况不必要捕获异常,所以你习惯用它也无所谓。
而在设计时,为了满足某些需求,有时候是必须使用TypeDescriptor的。TypeDescriptor是VisualStudio设计时的核心之一,关于TypeDescriptor的原理以及用法我今后将使用更多的文章深入介绍,敬请关注。