Dotnet专业组件开发揭密(二)
--TypeDescriptor内部机制及其应用(上)
本篇要讲述的是System.ComponentModel空间里最神奇的类,也是最重要的类,几乎所有的高级控件设计人员都必需掌握的类—TypeDescriptor。它的作用类似于反射,但是它是可以动态更改类信息的!虽然我们可能很少碰到直接使用它的情况,但是它的原理必须理解,这样你才不会对VS中一些机制产生疑问。
我们知道元数据可以说是Dotnet的基础,一旦编译后,就不能更改,而我们经常使用System.Reflection命名空间里的一些类来查看这些元数据,但是我们并没有办法去更改这些元数据。但是往往在设计阶段我们有这个需要,比如我们对某个控件要求在设计时确定一个属性的初使值,但是这个属性在运行时是不充许改变的,或者根本就不能公开,有没有什么好办法呢?再问你一个问题,以前的ActiveX组件在导入Dotnet环境后,你想过没有,原来的ActiveX控件是没有所谓的元数据的,哪为什么我们可以通过“属性窗口”来查看它的属性呢?答案还是TypeDescriptor起了作用。
说起TypeDescriptor,就不能不说到它最主要的“客户”—PropertyGrid,这个强大的微软居然遮遮掩掩不愿意直接公开的控件(呵呵,是不是很长的定语呀),以及一个非常有用的接口ICustomTypeDescriptor。嗯,我们先来说说PropertyGrid,从其名字就可以知道它可以显示一个类实例的属性,我们知道它只能显示一个类的属性(Property),但并不能显示公共字段。我们将在后面介绍如何实现让它显示公共字段。ICustomTypeDescriptor,则是为对象提供动态自定义类型信息的接口。不要小看了这句话,神奇的功能都是它实现的。
它们三者的关系是这样子的,当用PropertyGrid来查看一个类的属性信息时,它并不是我们一般认为的会通过Reflection空间里的类来进行反射,找到元数据,然后显示出来。实际上它利用TypeDescriptor来查看类实例的属性信息。而TypeDescriptor它的内部有几个逻辑判断,看看A)这个类是不是实现了ICustomTypeDescriptor接口,如果是它会用这个接口来返回属性信息,如果没有它会用默认的反射来返回;B)它会查看同一个逻辑容器里是不是有实现了IextenderProvider接口的类,如果有它会进行特殊处理;C)如果需要过滤一些属性,它会过滤掉。正因为它内部有如此复杂的机制,那么我们便可以通过第一步或第二步来影响返回的属性信息。实际上这里大家就知道了我们上一篇文章里为什么扩展机制如何起作用了。当然TypeDescriptor远不止我们上面所简述的这么简单,我们会在后面的篇章里深入探索它。
现在我们来回答关于ActiveX控件属性的问题。实际上,当我们拖入一个ActiveX组件时,VS会为我们产生一个包装类,它继承于System.Windows.Forms.AxHost这个纯虚的类,而这个类正好实现了ICustomTypeDescriptor,因此“属性窗口”通过TypeDescriptor来查看这个ActiveX组件时,它发现它实现了ICustomTypeDescriptor接口,因此它为用这个接口来取得组件的属性信息。从而实现了可以取得ActiveX组件的属性信息。下面的图就是ActiveX控件TreeView在VS环境下显示的属性情况。
我们现在来完成前面所说的如何在PropertyGrid里显示公共字段,因为我们将在以后的揭密里再对它进行更有趣的应用。
我们来看一个简单的职员类
public class Employee
{
public string Name;
public bool Sex;
public DateTime Born;
}
另外还有一个用属性实现的类EmployeeByProperty以及一个用ICustomTypeDescriptor接口实现的类EmployeeByICustomTypeDescriptor,它实际是继承于FieldToPropertyTypeDescriptor这个虚继类,由它来实现接口。另外还提供了一个可以动态包装Employee,可以使它同样达到EmployeeByICustomTypeDescriptor的包装代理类FieldToPropertyProxyTypeDescriptor,两个黑体显示的类,稍作一些修改,可以相当的有用。当然还有一个类FieldPropertyDescriptor也是必需要有的,它继承于PropertyDescriptor,用于描述具体的某个属性信息。
需要稍作说明的是FieldToPropertyTypeDescriptor这个类,当TypeDescriptor利用它返回属性集合时,首先它会用TypeDescriptor.GetProperties取得默认的元数据,然后再将每个字段信息转换成FieldPropertyDescriptor,然后加入属性集合里。下面便是这一小段代码的核心:
props = new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this, attributes, true))
{
props.Add(prop);
}
foreach (FieldInfo field in GetType().GetFields())
{
FieldPropertyDescriptor fieldDesc = new FieldPropertyDescriptor(field);
if (!filtering || fieldDesc.Attributes.Contains(attributes)) props.Add(fieldDesc);
}
好了,其它还有疑问的地方我们可以通过MSDN进行查找。不过顺便说一句,这种做法并不是在2.0里的推荐做法,我们会在下面看到2.0里的做法。
参考资料:MSDN Magazine关于ICustomTypeDescriptor的文章(http://msdn.microsoft.com/msdnmag/issues/05/04/NETMatters/),有兴趣大家可以看一看。
前面说明了如何利用ICustomTypeDescriptor来使PropertyGrid所看到的类属性动态变化,并且实现了一个将原来不能在PropertyGrid显示的类字段当作类属性显示出来的一个小程序。我们也讲到那是1.0里的做法,2.0里并不推荐使用,那么2.0里的方法应该是如何的。
首先我们来看1.0里的做法有些什么样的问题,首先是职责不清,不符合设计模式的观点,一个类既要实现它本有的功能,又要实现如何在PropertyGrid里实示,从软件设计的角度来看是相当不合理的(不过上一篇提出的代理类的方法倒是可以解决这个问题);其次是性能问题,为了实现这个目的,你的功能类要多出很多对象,从而提高性能,但是这样子做,无形中你的功能类负担太重了,而且更重要是因为每次需要通过反射和相关的处理来取得类信息,因此性能也不是很好。正是基于这两个原因,MS在2.0里提出了新的解决办法。
2.0里你还可以通地上一篇介绍的方法使用来达到你的目的,不过更好的是使用一个新类TypeDescriptionProvider以及一个辅助属性类TypeDescriptionProviderAttribute,它们可以解决上面的两个问题。
同样的你还是需要实现一个属性描述符类,我们借用上一篇的FieldPropertyDescriptor类,还需要实现一个用户自定的TypeDescriptor,2.0里你不需要从IcustomTypeDescirptor继承了,建议从CustomTypeDescriptor虚基类继承,你可以少写很多代码,我们实现了一个跟上一篇类似的FieldToPropertyTypeDescriptor类,但是它也有几点不同,首先它不再是一个虚基类,用户的功能类也不需要从此类继承,更多的,它象上一篇里的FieldToPropertyProxyTypeDescriptor这个类,最后你只要简单实现一个从TypeDescriptionProvider继承的类,它的作用就是返回一个提供者就可以了。
上面的阐述可能你会有点晕,我们还是来看看这张图,它显示了2.0里PropertyGrid如何来显示一个类的属性
下面的图是是DEMO代码的运行界面,它除了一个PropertyGrid外,还多了两个按钮,可以删除和增加提供者,同时你可以看到PropertyGrid的变化。
最后需要提醒大家一句的是,真正用到TypeDescriptionProvider的地方并不多,也只是在PropertyGrid里显示时用一下。很多控件的通用显示还是通过查看类是否实现了ICustomTypeDescriptor接口来决定显示类的信息,比如我们2.0里常用的DataGridView控件,它支持显示实现的ICustomTypeDescriptor接口的类实例。这个大家可以按上一篇介绍的方法自已试一试。
在下一篇里我们将通过Reflector来探索TypeDescriptor的内部机制。
参考文档:MSDN Magazine关于ICustomTypeDescriptor的文章(http://msdn.microsoft.com/msdnmag/issues/05/05/NETMatters/)。
相关代码参考:https://files.cnblogs.com/mywebname/TypeDescriptionProvider.rar