检测定制特性
定义一个特性类型本身没有什么用处,应用程序代码的行为不会因此有任何改变。
在枚举类型上应用Flags特性可以改变System.Enum.ToString、Format方法的行为,这是因为它们会在运行时检测所操作的枚举类型上是否应用了Flags特性。应用程序利用反射技术来查找目标元素上应用了哪些特性。
如果我们是微软公司负责实现Enum类型中Format方法的员工,我们可能会像下面这样来实现它:
{
// 检测传入的枚举类型是否应用了Flags类型的实例
if (enumType.IsDefined( typeof (FlagsAttribute), false ))
{
// 定义了Flags特性的操作
}
else
{
// 未定义Flags特性的操作
}
}
上面代码调用了Type.IsDefined方法,它会查寻枚举类型的元数据来判断其上是否应用了Flags特性。
依上述代码所见,如果我们定义了自己的特性类型,我们还必须实现某些代码来检测目标元素上是否应用了这些特性类型的实例,然后根据检测结果执行相应的代码路径。只有这样,定制特性才有意义。
有时,我们可能希望特性检测的目标元素不是一个类型,而是一个程序集、一个模块或一个方法。System.Attribute类型定义了3个静态方法来获取应用于目标元素上的特性:IsDefined、GetCustomAttributes、GetCustomAttribute。每个方法有好几个重载版本,允许我们检测各种不同目标元素上的定制特性。这些方法都是通过在元素数据上执行某种反射操作来查询与CLS兼容的定制特性。
IsDefined:如果至少有一个指定的Attribute的派生类型应用在目标元素上,该方法返回true。该方法执行速度比较快,因为它不需要构造(反序列化)任何特性类型的实例。
GetCustomAttributes:该方法返回一个数组,其中元素是指定的、应用在目标元素上的特性实例。每个实例都会用编译期间指定的参数、字段、属性来构造(反序列化)。如果目标元素上没有应用指定的特性实例,方法将返回一个空数组。该方法通常用于那些将AllowMultiple设为true的特性。
GetCustomAttribute:该方法返回一个指定的、应用在目标元素上的特性实例。返回的实例使用编译期间指定的参数、字段、属性来构造(反序列化)。如果目标元素上没有应用指定的特性实例,方法抛出一个System.Reflection.AmbiguousMatchEexception异常。该方法通常用于那些将AllowMultiple设为false的特性。
另外,System.Reflection命名空间中定义的一些类型还允许我们查看一个模块中的元数据内容。这些类型也提供了IsDefined和GetCustomAttributes方法。
还有一个问题要注意:当我们向IsDefined、GetCustomAttribute、GetCustomAttributes传递一个类型时,这些方法会在应用程序中搜索我们指定的特性类型及其所有的派生类型。如果我们的代码找到一个具体的特性类型,我们应该对返回值做一个额外的检查,以确保这些方法返回的类型就是我们正在搜索的类型。建议大家将自己的特性类型定义为密封类型,从而减少潜在的混淆并省去这种额外的检查。
伪定制特性
微软定义的某些特性应用十分频繁,如果将所有这些特性信息都存放到元数据中将导致生成托管模块的大小急剧膨胀。因此,这些特性在编译时都经过了特殊处理,它们以位的形式存放在元数据中。CLR和FCL也知道怎样在元数据中以一种特殊的方式来查找这些伪定制特性。
如Serializable特性就是一个伪特性类型,示例代码如下:
class SomeType
{......}
因为Serializable特性很常用,编译器会将它以一个位的形式编译到元数据中,而不必将Serializable特性实例的所有元数据都编译进去。
关于伪定制特性最重要的一点是我们不能在运行时像检测通常的定制特性那样的试来检测它们是否存在。目前来讲,我们只能采用其他一些方式来检测代码中是否应用了这些伪定制特性。如System.Type提供了一些只读属性IsSerializable、IsAutoLayout、IsExplicitLayout等来检测类型上是否应用了这些伪定制特性。但对于大多数伪定制特性,FCL中并没有定义允许我们检测它们是否存在的一些类型或方法。有一种方式可以帮助我们检测伪定制特性是否存在,即用非托管代码来直接访问CLR的COM接口。