24.2.2 创建自定义特性

要创建自定义特性,需要以下几个步骤:

❑声明一个特性类,必须为公共类,另外,该类直接或间接地继承自System.Attribute类;

❑为该特性类应用AttributeUsageAttribute特性,由于该特性的作用是专用于定义其他特性,因此也可称之为“元特性”;

❑定义特性类的公共构造函数,构造函数的参数将作为特性的定位参数;

❑定义属性,属性必须可读写,不能为只读或只写。属性将作为特性的命名参数。

我们知道,特性类构造函数中的参数将作为特性的定位参数,一个类的构造函数可以有多个重载,这些重载的构造函数可以适应值的不同组合。如果同时为自定义特性类定义了属性,则在初始化特性时可使用命名参数和定位参数的组合。在通常情况下,将所有必选参数定义为定位参数,将所有可选参数定义为命名参数。在这种情况下,如果没有必选参数,则无法初始化特性。其他所有参数都是可选参数。

图24-14展示的就是一个自定义的特性,它有一个不带参数的默认构造函数和一个公共属性,这意味着该特性没有定位参数,以及拥有一个命名参数。

24.2.2 创建自定义特性 - 图1

图 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所示。

24.2.2 创建自定义特性 - 图2

我们继续研究AllowMultiple命名参数,探讨它对于所修饰的特性有何影响。AllowMultiple属性指示元素中是否可存在特性的多个实例。如果设置为true,则允许存在多个实例;如果设置为false(默认值),则只允许存在一个实例。如代码清单24-15所示,我们定义了一个特性MultipleEnableAttribute,设置AllowMultiple为true,意味着它可以在同一个程序元素应用多个实例。

代码清单24-15 支持多实例的特性演示

24.2.2 创建自定义特性 - 图3

接下来介绍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)。