我在后面将较详细地介绍 Type类,现在只希望你能对反射有个第一印象,所以只简略地作以说明:Type抽象类提供了访问类型元数据的能力,当你实例化了一个Type对象后,你可以通过它的属性和方法,获取类型的元数据信息,或者进一步获得该类型的成员的元数据。注意到这里,因为Type对象总是基于某一类型的,并且它是一个抽象类,所以我们在创建Type类型时,必须要提供 类型,或者类型的实例,或者类型的字符串值。
我们看一看完整的代码:
private static DataTable GetDataTable() {
Type enumType = typeof(BookingStatus); // 创建类型
FieldInfo[] enumFields = enumType.GetFields(); //获取字段信息对象集合
DataTable table = new DataTable();
table.Columns.Add("Name", Type.GetType("System.String"));
table.Columns.Add("Value", Type.GetType("System.Int32"));
// 遍历集合
foreach (FieldInfo field in enumFields) {
if (!field.IsSpecialName) {
DataRow row = table.NewRow();
row[0] = field.Name;
row[1] = Convert.ToInt32(field.GetRawConstantValue());
//row[1] = (int)Enum.Parse(enumType, field.Name); //也可以这样
table.Rows.Add(row);
}
}
return table;
}
使用泛型来达到代码重用
观察上面的代码,如果我们现在有另一个枚举,叫做TicketStatus,那么我们要将它绑定到列表,我们唯一需要改动的就是这里:
Type enumType = typeof(BookingStatus); //将BookingStatus改作TicketStatus
既然这样,我们何不定义一个泛型类来进行代码重用呢?我们管这个泛型类叫做EnumManager<TEnum>。
public static class EnumManager<TEnum>
{
private static DataTable GetDataTable()
{
Type enumType = typeof(TEnum); // 获取类型对象
FieldInfo[] enumFields = enumType.GetFields();
DataTable table = new DataTable();
table.Columns.Add("Name", Type.GetType("System.String"));
table.Columns.Add("Value", Type.GetType("System.Int32"));
//遍历集合
foreach (FieldInfo field in enumFields)
{
if (!field.IsSpecialName)
{
DataRow row = table.NewRow();
row[0] = field.Name;
row[1] = Convert.ToInt32(field.GetRawConstantValue());
//row[1] = (int)Enum.Parse(enumType, field.Name); 也可以这样
table.Rows.Add(row);
}
}
return table;
}
public static void SetListControl(ListControl list)
{
list.DataSource = GetDataTable();
list.DataTextField = "Name";
list.DataValueField = "Value";
list.DataBind();
}
}
OK,现在一切都变得简便的多,以后,我们再需要将枚举绑定到列表,只要这样就行了(ddl开头的是DropDownList,rbl开头的是RadioButtonList):
EnumManager<BookingStauts>.SetListControl(ddlBookingStatus);
EnumManager<TicketStatus>.SetListControl(rblTicketStatus);
反射特性(Attribute):自定义特性(Custom Attributes)
其次,我们发现在这个特性的定义上,又用了三个特性去描述它。这三个特性分别是:Serializable、AttributeUsage 和 ComVisible。Serializable特性我们前面已经讲述过,ComVisible简单来说是“控制程序集中个别托管类型、成员或所有类型对 COM 的可访问性”(微软给的定义)。这里我们应该注意到: 特性本身就是用来描述数据的元数据,而这三个特性又用来描述特性,所以它们可以认为是“元数据的元数据”(元元数据:meta-metadata)。然后我们看一下AttributeUsage的定义:
namespace System {
public sealed class AttributeUsageAttribute : Attribute {
public AttributeUsageAttribute(AttributeTargets validOn);
public bool AllowMultiple { get; set; }
public bool Inherited { get; set; }
public AttributeTargets ValidOn { get; }
}
}
AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class);
usage.AllowMultiple = true; // 设置AllowMutiple属性
usage.Inherited = false;// 设置Inherited属性
public class DemoClass{
// ClassBody
}
可以看到,它有一个构造函数,这个构造函数含有一个AttributeTargets类型的位置参数(Positional Parameter),还有两个命名参数(Named Parameter)。注意ValidOn属性不是一个命名参数,因为它不包含set访问器。
这里大家一定疑惑为什么会这样划分参数,这和特性的使用是相关的。假如AttributeUsageAttribute 是一个普通的类,我们一定是这样使用的:
// 实例化一个 AttributeUsageAttribute 类
但是,特性只写成一行代码,然后紧靠其所应用的类型(目标类型),那么怎么办呢?微软的软件工程师们就想到了这样的办法:不管是构造函数的参数 还是 属性,统统写到构造函数的圆括号中,对于构造函数的参数,必须按照构造函数参数的顺序和类型;对于属性,采用“属性=值”这样的格式,它们之间用逗号分隔。于是上面的代码就减缩成了这样:
[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]
可以看出,AttributeTargets.Class是构造函数参数(位置参数),而AllowMutiple 和 Inherited实际上是属性(命名参数)。命名参数是可选的。将来我们的RecordAttribute的使用方式于此相同。(为什么管他们叫参数,我猜想是因为它们的使用方式看上去更像是方法的参数吧。)
假设现在我们的RecordAttribute已经OK了,则它的使用应该是这样的:
[RecordAttribute("创建","张子阳","2008-1-15",Memo="这个类仅供演示")]
其中recordType, author 和 date 是位置参数,Memo是命名参数。
AttributeTargets 位标记
从AttributeUsage特性的名称上就可以看出它用于描述特性的使用方式。具体来说,首先应该是其所标记的特性可以应用于哪些类型或者对象。从上面的代码,我们看到AttributeUsage特性的构造函数接受一个 AttributeTargets 类型的参数,那么我们现在就来了解一下AttributeTargets。
AttributeTargets 是一个位标记,它定义了特性可以应用的类型和对象。
[Flags]
public enum AttributeTargets {
Assembly = 1, //可以对程序集应用属性。
Module = 2, //可以对模块应用属性。
Class = 4, //可以对类应用属性。
Struct = 8, //可以对结构应用属性,即值类型。
Enum = 16, //可以对枚举应用属性。
Constructor = 32, //可以对构造函数应用属性。
Method = 64, //可以对方法应用属性。
Property = 128, //可以对属性 (Property) 应用属性 (Attribute)。
Field = 256, //可以对字段应用属性。
Event = 512, //可以对事件应用属性。
Interface = 1024, //可以对接口应用属性。
Parameter = 2048, //可以对参数应用属性。
Delegate = 4096, //可以对委托应用属性。
ReturnValue = 8192, //可以对返回值应用属性。
GenericParameter = 16384, //可以对泛型参数应用属性。
All = 32767, //可以对任何应用程序元素应用属性。
}
现在应该不难理解为什么上面我范例中用的是:
[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]
而ObsoleteAttribute特性上加载的 AttributeUsage是这样的:
[AttributeUsage(6140, Inherited = false)]
因为AttributeUsage是一个位标记,所以可以使用按位或“|”来进行组合。所以,当我们这样写时:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)
意味着既可以将特性应用到类上,也可以应用到接口上。
使用反射查看自定义特性
利用反射来查看 自定义特性信息 与 查看其他信息 类似,首先基于类型(本例中是DemoClass)获取一个Type对象,然后调用Type对象的GetCustomAttributes()方法,获取应用于该类型上的特性。当指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一个参数attributeType时,将只返回指定类型的特性,否则将返回全部特性;第二个参数指定是否搜索该成员的继承链以查找这些属性。
class Program {
static void Main(string[] args) {
Type t = typeof(DemoClass);
Console.WriteLine("下面列出应用于 {0} 的RecordAttribute属性:" , t);
// 获取所有的RecordAttributes特性
object[] records = t.GetCustomAttributes(typeof(RecordAttribute), false);
foreach (RecordAttribute record in records) {
Console.WriteLine(" {0}", record);
Console.WriteLine(" 类型:{0}", record.RecordType);
Console.WriteLine(" 作者:{0}", record.Author);
Console.WriteLine(" 日期:{0}", record.Date.ToShortDateString());
if(!String.IsNullOrEmpty(record.Memo)){
Console.WriteLine(" 备注:{0}",record.Memo);
}
}
}
}