在Microsoft.Net平台上,public、private、static等特性可以被应用于各种类型和成员。如果允许创建用户自定义的特性,并将它们应用于类型和方法上对于编程工作显然是一件非常美妙和方便的事情。微软推出的定制特性机制就是用来允许开发人员使用用户自定义的特性。该机制能够保证所有面向通用语言运行时(CLR)的编译器都能够识别定制特性,并将它们存放在生成的元数据中。在本文中,我将解析Microsoft.Net定制特性的相关知识。
一、特性的实质
.NET
框架类库(
FCL
)提供了很多预定义特性。例如,
System.FlagsAttribute
特性允许我们将一个枚举类型看作一组标记,
System.SerializableAttribute
特性允许一个类型的字段被序列化和反序列化,几个安全相关的特性可以确保方法在试图进行某种特殊访问的时候具有要求的特权级别,许多互操作相关的特性可以帮助托管代码调用非托管代码,等等。
对于定制特性,我们首先需要知道的是它们仅仅是为目标元素提供关联附加信息的一种方式。编译器的工作只是将这些附加的信息存放在托管模块的元数据中而已。按理说
CLR
允许将特性应用于任何可以在一个文件的元数据中表示的元素,但是大多数语言只允许将它们应用到各种元数据定义表中的条目上,其中包括微软的
C#
。
实际上,一个特性仅仅是一个类型的实例,添加一个特性就是构造一个特性类的对象实例。下面以常用的
DllImport
特性为例分析一下特性实例的构造:
[DllImport("Kernel32", CharSet=CharSet.Auto, SetLastError=true)]
每次使用该特性就相当于调用 DllImportAttribute 类型的公有构造器创建了一个实例。如果我们查看 DllImportAttribute 类型的文档,我们会发现它的构造器只要求一个 String 参数。例子中, "Kernel32" 被作为这样的参数传递进去。一个特性类型的构造器参数被称为定位参数,该参数是强制性的,也就是说在应用特性时必须制定这样的参数。那么其他两个 “ 参数 ” 呢?特殊的语法允许我们在对象构造之后设置它的公有字段或属性。因此,当 DllImportAttribute 对象被构造时,实际上发射个三件事情: "Kernel32" 被传入 DllImportAttribute 构造器, CharSet 被设为 CharSet.Auto , SetLastError 被设为 true 。设置字段或属性的 “ 参数 ” 被称作命名参数,它们往往是可选的,并且必须有缺省值。
依据通用语言规范(
CLS
),定制特性的类型必须直接或间接继承自
System.Attribute
,并且所有特性类型的名称都应该有一个
“Attribute”
后缀。另为所有的非抽象特性都必须具有
public
访问限制,并且至少包含一个公有构造器。下面是
FCL
中
AttributeUsageAttribute
类型的实现源码:
[AttributeUsage(AttributeTargets.Class, Inherited
=
false
)]
[Serializable]
public sealed class AttributeUsageAttribute : Attribute
{
internal AttributeTargets m_attributeTarget = AttributeTargets.All;
internal Boolean m_allowMultiple = false ;
internal Boolean m_inherited = true ;
public AttributeUsageAttribute(AttributeTargets validOn)
{
m_attributeTarget = validOn;
}
public AttribteTargets ValidOn
{
get { return m_attributeTarget; }
}
public Boolean AllowMultiple
{
get { return m_allowMultiple; }
set { m_allowMultiple = value; }
}
public Boolean Inherited
{
[Serializable]
public sealed class AttributeUsageAttribute : Attribute
{
internal AttributeTargets m_attributeTarget = AttributeTargets.All;
internal Boolean m_allowMultiple = false ;
internal Boolean m_inherited = true ;
public AttributeUsageAttribute(AttributeTargets validOn)
{
m_attributeTarget = validOn;
}
public AttribteTargets ValidOn
{
get { return m_attributeTarget; }
}
public Boolean AllowMultiple
{
get { return m_allowMultiple; }
set { m_allowMultiple = value; }
}
public Boolean Inherited
{