24.2.2 创建自定义特性
要创建自定义特性,需要以下几个步骤:
❑声明一个特性类,必须为公共类,另外,该类直接或间接地继承自System.Attribute类;
❑为该特性类应用AttributeUsageAttribute特性,由于该特性的作用是专用于定义其他特性,因此也可称之为“元特性”;
❑定义特性类的公共构造函数,构造函数的参数将作为特性的定位参数;
❑定义属性,属性必须可读写,不能为只读或只写。属性将作为特性的命名参数。
我们知道,特性类构造函数中的参数将作为特性的定位参数,一个类的构造函数可以有多个重载,这些重载的构造函数可以适应值的不同组合。如果同时为自定义特性类定义了属性,则在初始化特性时可使用命名参数和定位参数的组合。在通常情况下,将所有必选参数定义为定位参数,将所有可选参数定义为命名参数。在这种情况下,如果没有必选参数,则无法初始化特性。其他所有参数都是可选参数。
图24-14展示的就是一个自定义的特性,它有一个不带参数的默认构造函数和一个公共属性,这意味着该特性没有定位参数,以及拥有一个命名参数。
图 24-14 自定义特性
其中,我们重点关注AttributeUsageAttribute元特性,它用于指定另一特性类的用法。它有如下三个参数,其中一个定位参数和两个命名参数:
❑定位参数validOn,它是AttributeTargets枚举类型,该枚举包括了该特性可应用的所有可能元素的集。使用按位“或”运算可以组合多个AttributeTargets值,以获取所需的有效程序元素组合。
❑命名参数AllowMultiple,该命名参数指定能否为给定的程序元素多次应用所指示的特性。
❑命名参数Inherited,该命名参数指定所指示的特性能否由派生类和重写成员继承。
定位参数validOn位于特性类的公共构造函数中,每个公共构造函数都为该类定义一个有效的定位参数序列,如代码清单24-14所示。
代码清单24-14 定位参数validOn
public AttributeUsageAttribute(AttributeTargets validOn)
{
this.m_attributeTarget=AttributeTargets.All;
this.m_inherited=true;
this.m_attributeTarget=validOn;
}
这里我们有必要说明一下AttributeTargets枚举类型,它的每个值都表示应用特性的应用程序元素,AttributeTargets枚举类型的值如表24-3所示。
我们继续研究AllowMultiple命名参数,探讨它对于所修饰的特性有何影响。AllowMultiple属性指示元素中是否可存在特性的多个实例。如果设置为true,则允许存在多个实例;如果设置为false(默认值),则只允许存在一个实例。如代码清单24-15所示,我们定义了一个特性MultipleEnableAttribute,设置AllowMultiple为true,意味着它可以在同一个程序元素应用多个实例。
代码清单24-15 支持多实例的特性演示
接下来介绍Inherited定位参数,该参数用途为指示特性是否可由应用了该特性的类的派生类来继承。该属性的默认值为true。照例,我们仍然使用一个例子进行说明。首先定义两个特性:FirstAttribute和SecondAttribute,前者设定Inherited参数值为true,后者为false。将这两个特性同时应用到BaseClass类,它是一个基类,然后定义一个TargetClass类,它从BaseClass类派生,是子类。接下来就可以在运行时,通过反射来查找TargetClass类实例所应用的特性了,这里要注意的一点是,GetCustomAttributes方法的参数使用true,而不是false,这表示搜索该成员的继承链以查找这些特性,因为TargetClass类所应用的特性来源于它的基类BaseClass,如代码清单24-16所示。
代码清单24-16 支持继承的特性演示
[AttributeUsage(AttributeTargets.All,Inherited=true)]
public class FirstAttribute:Attribute
{
}
[AttributeUsage(AttributeTargets.All,Inherited=false)]
public class SecondAttribute:Attribute
{
}
[First]
[Second]
public class BaseClass
{
}
public class TargetClass:BaseClass
{
}
public class MyProgram
{
public static void Main()
{
TargetClass cls=new TargetClass();
//获得Type实例
Type type=cls.GetType();
//获得自定义特性数组,参数为true表示搜索该成员的继承链以查找这些特性
object[]attrs=type.GetCustomAttributes(true);
//遍历每一个特性
foreach(object attr in attrs)
{
//判断是否为FirstAttribute特性的实例
if(attr is FirstAttribute)
{
Console.WriteLine("FirstAttribute found.");
}
//判断是否为SecondAttribute特性的实例
else if(attr is SecondAttribute)
{
Console.WriteLine("SecondAttribute found.");
}
else
{
Console.WriteLine("No attribute found.");
}
}
}
}
上述代码的运行结果为:
FirstAttribute found.
请按任意键继续……
接下来,定义两个特性类作为举例,分别为TableAttribute和ColumnAttribute、这两个特性应用在某些实体类上,用以模拟O/R Mapping框架和数据库进行关联。TableAttribute特性只能应用到类,并且一个类只能应用一个特性,不支持继承,它的作用是将类和数据库的表建立关联,其代码如代码清单24-17所示。
代码清单24-17 TableAttribute特性的源代码
[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]
public class TableAttribute:Attribute
{
public bool IsLazy;
private string_name;
public TableAttribute(string name)
{
this._name=name;
}
public string Name
{
get
{
return_name;
}
}
}
ColumnAttribute特性也和TableAttribute特性一样,不允许在一个程序元素应用多次,也不支持继承。但不同的是,ColumnAttribute特性限定只能应用在属性上。ColumnAttribute特性的作用是将实体类的属性成员和数据库表的字段之间建立关联,用以说明某个实体类的属性成员所对应的字段的名称、长度、类型、是否可为NULL等信息。这里把字段名称和类型设为必选参数,因此,columnName和columnType这两个参数位于特性类的公共构造函数,作为定位参数;其余的为属性,作为命名参数,如代码清单24-18所示。
代码清单24-18 ColumnAttribute特性的源代码
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=false),]
public class ColumnAttribute:Attribute
{
private string_columnName;
private string_columnType;
//构造函数
public ColumnAttribute(string columnName,string columnType)
{
_columnName=columnName;
_columnType=columnType;
}
//只读属性
public string ColumnName
{
get
{
return_columnName;
}
}
//只读属性
public string ColumnType
{
get
{
return_columnType;
}
}
//可读写属性
public int ColumnLength
{
get;
set;
}
//可读写属性
public bool IsNull
{
get;
set;
}
}
有了这两个特性,我们把它应用到一个实体类,如代码清单24-19所示。
代码清单24-19 应用TableAttribute和ColumnAttribute特性到实体类Book
[Table("TBook",IsLazy=true)]
class Book
{
[Column("bookId","int",ColumnLength=8,IsNull=true)]
public int BookId
{
get;
set;
}
[Column("bookName","varchar",ColumnLength=25,IsNull=true)]
public string BookName
{
get;
set;
}
[Column("bookAuthor","varchar",ColumnLength=15,IsNull=true)]
public string Author
{
get;
set;
}
[Column("bookPubYear","datetime",ColumnLength=10,IsNull=true)]
public DateTime PubYear
{
get;
set;
}
}
[1]Module指的是可移植的可执行文件(.dll或.exe)。