.NET特性与反射(转)
转载自:http://blog.csdn.net/byondocean/article/details/6802111
.NET编译器的任务之一是为所有定义和引用的类型生产元数据描述。除了程序集中标准的元数据外,.NET平台允许程序员使用特性(attribute)把更多的元数据嵌入到程序集中。简而言之,特性就是用于类型(比如类、接口、结构等)、成员(比如属性、方法等)、程序集或模块的代码注释。
当浏览.NET命名空间时,将发现许多预定义特性,可以在应用程序中使用它们。此外,可以创建自定义特性,通过从Attribute派生出新类型进一步修饰类型的行为。当在代码中应用特性时,如果它们没有被另一个软件显示地反射,那么嵌入的元数据基本没什么作用。反之,嵌入程序集的元数据介绍将被忽略不计,而并无害处。
限制特性使用:有时候需要建立这样一个自定义特性,它只能被应用到选定的代码元素上。如果希望限制自定义特性的应用范围,需要在自定义特性的定义中应用[AttributeUsage]特性。[AttributeUsage]特性支持AttributeTargets枚举值得任意组合(通过OR操作)。
自定义特性:你可以随时创建自己
声明一个特性:和C#的大多数元素一样,特性是由类来实现的。要创建一个自定义特性,你须要从System.Attribute类派生你新的自定义特性的类。
public class BugFixAttribute:System.Attribute
你须要告诉编译器这个特性可以被用在那种类型的元素上(特性目标)。使用 特性可以说明这一信息。
[AttributeUsage(AttributeTargets.Class|
AttributeTargets.Constructor|
AttributeTargets.Field|
AttributeTargets.Method|
AttributeTartets.Property|
AllowMultiple=true)]
AttrbuteUsage特性是一个应用在特性上的特性,也就是一个元特性。也可以说,它提供了元-元数据,也就是于元数据相关的数据。你可以给AttributeUsage传递两个参数。
第一个参数是一个标志集合,它指明了特性的目标类型,在这个例子中,它们分别是类和 构造函数、字段、方法和属性。第二个参数是一个用来指明特定的元素是否可以接受多个这样的特性的 标记。在这个例子中,AllowMultiple被设置为True,这表明了类的成员可以应用多个BugFixAttribute特性。
构造一个特性:特性可以接受两种类型的参数:位置参数和命名参数。在BugFix特性的例子中,程序员的名字、Bug ID 和日期都是位置型参数,而备注是命名参数。位置型参数是通过构造函数传入的。它们必须按照构造函数中声明的顺序传入。
- public BugFixAttribute(int bugID, string programmer, string date)
- {
- this.BugID = bugID;
- this.Programmer = programmer;
- this.Date = date;
- }
命名参数是使用字段或属性的形式来实现的:
public string Comment { get; set; }
通常也可以为位置参数创建只读属性:
public int BugID { get; private set; }
使用特性
定义好特性之后,通过将它放在目标的前面,就可以让它起作用了。为了测试前一示例的 BugFixAttribute特性,以下的程序创建一个名为MyMath的简单类,并为它提供了两个函数。另外,还给这个类设置了BugFixAttribute特性以记录代码维护的历史信息:
[BugFixAttribute(121,"Jesse Liberty","01/03/08")]
[BugFixAttribute(107,"Jesse Liberty","01/04/08",Commet="Fixed off by one errors")]
public class MyMath
这些特性值将被存储到元数据中。
- [AttributeUsage(AttributeTargets.Class|
- AttributeTargets.Constructor|
- AttributeTargets.Field|
- AttributeTargets.Module|
- AttributeTargets.Property,
- AllowMultiple=true)]
- public class BugFixAttribute:System.Attribute
- {
- //具有特定位置參數的特性構造函數
- public BugFixAttribute(int bugID, string programmer, string date)
- {
- this.BugID = bugID;
- this.Programmer = programmer;
- this.Date = date;
- }
- //访问器
- public int BugID { get; private set; }
- public string Programmer { get; private set; }
- public string Date { get; private set; }
- //命名参数的属性
- public string Comment { get; set; }
- }
- //************将特性赋给类****************
- [BugFixAttribute(121,"Jesse Liberty", "01/03/08")]
- [BugFixAttribute(107, "Jesse Liberty", "01/04/08", Comment = "Fixed off by one errors")]
- public class MyMath
- {
- public double DoFunc1(double param1)
- {
- return param1 + DoFunc2(param1);
- }
- public double DoFunc2(double param1)
- {
- return param1 / 3;
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- MyMath mm = new MyMath();
- Console.WriteLine("Calling DoFunc(7).Result:{0}", mm.DoFunc1(7));
- Console.Read();
- }
- }
反射(Reflection)
近年来,Reflection已经成为主流语言中必备的特色,其主要用途是执行时期提供类型信息,一旦拥有这些信息,设计人员可以轻易地创建出具备动态解析能力的应用程序,例如在执行时期以一个字符串创建起对应的对象,抑或是以一个字符串来调用函数,都可以由Reflection技术来达成。Reflection技术同时也是RAD开发工具的幕后功臣,运用Reflection技术,RAD开发工具可以取出某个组件的属性与事件等信息显示于属性表之上。在某些特殊应用上,Reflection更是扮演着极关键的角色,例如设计人员可以用Reflection取得某个类的信息,再搭配.NET的CodeDOM技术来产生一个继承至该类的类源代码,动态为其实现某个接口,或是覆写某个函数,抑或是结合Script语言来产生一个符合特定结构需求的对象。.NET Framework,Reflection是经由Type对象来操作,其中分成两部分,一部分是提供该Type本身的信息,例如Public、Sealed\Serializable、Attributes、Interfaces等等。此部分还算相当直观,此处就不在赘述,另一部分则是取得该Type内的成员信息,如字段GetField(s)
、属性GetProperty、事件GetEvent(s) 、Attributes(GetCustomAttibutes ),这一系列函数的返回值皆是MemberInfo类或其子嗣。
MemberInfo类的子嗣:
FieldInfo类代表成员变量,PropertyInfo类代表属性,EventInfo类代表事件,MethodBase类细分为两部分,ConstructorInfo类代表创建函数,MethodInfo类代表成员函数。
有趣的是Type类本身也是MemberInfo类的子嗣,这种设计代表着Nested Type(外围类型)也是Member的一员。
下面列举Type类型的常用方法:
函数 说明
GetConstructor(s) 取得此类型的创建函数,其将回传一个ConstructorInfo对象或数组
GetField(s) 取得此类型中成员变量,其将回传一个FiledInfo对象或数组
GetMember(s) 取得此类中的成员,其类型可以是变量、事件、属性、方法及Nested Type,其将回传一个MemberInfo对象或数组
GetEvent(s) 取得此类型中的事件,其将回传一个EventInfo对象或数组
GetProperty/GetProperties 取得此类型中的属性,其将回传一个PropertyInfo对象或数组
GetNestedType(s) 取得声明于此类型内类型,其将回传一个Type对象或数组
GetCustomAttibutes 取得绑定于此类型的Attitudes
利用这些函数,设计人员可以在执行时期取得某个类型中所有的成员信息,也可以在非默认对象类型的情况下调用其成员函数或是设定某属性值。
- /// <summary>
- /// 成员存取器工具类
- /// </summary>
- public static class MemberAccessorUtils
- {
- private static readonly IMemberAccessor[] EMPTY = new IMemberAccessor[0];
- /// <summary>
- /// Gets the accessors.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="bindingFlags">The binding flags.</param>
- /// <returns></returns>
- public static IMemberAccessor[] GetMemberAccessors(Type type, BindingFlags bindingFlags)
- {
- if (type == null || type == typeof(object))
- return EMPTY;
- FieldInfo[] fieldInfos = type.GetFields(bindingFlags);
- PropertyInfo[] propertyInfos = type.GetProperties(bindingFlags);
- IMemberAccessor[] memberAccessors = new IMemberAccessor[fieldInfos.Length + propertyInfos.Length];
- int i = 0;
- foreach (FieldInfo fieldInfo in fieldInfos)
- {
- memberAccessors[i++] = new FieldAccessor(fieldInfo);
- }
- foreach (PropertyInfo propertyInfo in propertyInfos)
- {
- memberAccessors[i++] = new PropertyAccessor(propertyInfo);
- }
- return memberAccessors;
- }
- /// <summary>
- /// Gets the accessor.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="name">The name.</param>
- /// <param name="bindingFlags">The binding flags.</param>
- /// <returns></returns>
- public static IMemberAccessor GetMemberAccessor(Type type, string name, BindingFlags bingdingFlags)
- {
- FieldInfo fieldInfo = type.GetField(name, bingdingFlags);
- if (fieldInfo != null)
- return new FieldAccessor(fieldInfo);
- PropertyInfo propertyInfo = type.GetProperty(name, bingdingFlags);
- if (propertyInfo != null)
- return new PropertyAccessor(propertyInfo);
- return null;
- }
- }
要让元数据中的特性真正起作用,你需要一种访问它们的方法,并且最好是在程序运行的时候。在Reflection命名空间中的类和System.Type类一起为你提供了检查和处理元数据的功能。
反射通常被用在以下4种任务中
查看元数据:这一功能可能会被希望显示元数据的工具和辅助程序使用。
执行类型发现功能:这一功能允许你检查程序集里的类型并处理或实例化这些类型。在创建自定义脚本时这一功能会非常有用。例如,你可能希望允许用户使用一种脚本语言和你的程序打交道,这种脚本语言包括Javascript或你自己创建的一种语言。
延迟绑定到方法和属性上:这一功能允许程序员基于类型发现的功能来调用动态实例化对象上的属性和方法。这也被称为动态调用。
在运行时创建类型(反射代码发射功能):反射最强大的用途是在运行时创建新的类型,然后使用这些类型执行任务。当一个任务在运行时创建的自定义类和运行速度比在编译期创建的更加通用的代码快得多的时候,你可能会这样做。
查看元数据
下面通过发射读取MyMath类中的元数据。一开始,你须要获得一个MemberInfo类的对象。位于System.Reflection命名空间的这一对象的作用在于发现成员的特性,并提供访问元数据的方法:
System.Reflection.MemberInfo inf = typeof(MyMath);
调用MyMath类型上typeof操作符将返回一个Type类型的对象,Type类型派生自MemberInfo类型。Type类是反射类的核心,它封装了对象类型的表达形式。Type类是访问元数据的主要方法。它派生自MemberInfo类并且封装了关于类的成员的信息(如方法、属性、字段、事件等信息)。
下一步就是调用这个MemberInfo对象上的GetCustomAttributes方法,传入你希望查找的特性的类型。你将获得一个对象的数据,数据中每一项的类型都是BugFixAttribute:
object[] attributes;
attributes = inf.GetCustomAttributes(typeof(BugFixAttribute), false);
- class Program
- {
- static void Main(string[] args)
- {
- MyMath mm = new MyMath();
- Console.WriteLine("Calling DoFunc(7).Result:{0}", mm.DoFunc1(7));
- //获得成员信息并使用它获取自定义特性
- System.Reflection.MemberInfo inf = typeof(MyMath);
- object[] attributes;
- attributes = inf.GetCustomAttributes(typeof(BugFixAttribute), false);
- //迭代访问特性,并获取属性
- foreach (Object attribute in attributes)
- {
- BugFixAttribute bfa = (BugFixAttribute)attribute;
- Console.WriteLine("\nBugID: {0}", bfa.BugID);
- Console.WriteLine("Programmer: {0}", bfa.Programmer);
- Console.WriteLine("Date: {0}", bfa.Date);
- Console.WriteLine("Comment: {0}", bfa.Comment);
- }
- Console.Read();
- }
- }
类型发现(Type DisCovery)
你可以使用反射功能来浏览并检查程序集里的内容。可以查找与模块相关的类型,于类型相关的方法、字段、属性和事件,以及类型中所用方法的签名、类型支持的接口和类型的基类等信息。
首先,你须要使用Assembly.Load()静态方法动态地加载一个程序集。Assembly类封装了实际的程序集自身,它提供了反射的功能。Load方法的其中一种签名如下:
public static Assembly.Load(AssmeblyName)
在下面例子中,你将核心库的名称传给了Load()方法。Mscorlib.dll包含了.NET框架的核心类:Assembly a = Assembly.Load("Mscorlib");
加载了程序集之后,你就可以调用GetTypes()方法,让它返回Type对象的数组。Type对象是反射功能的核心。它表示了类型的声明信息(其中包括类、接口、数组、值和枚举等):
Type[] types = a.GetTypes();
你可以使用一个Foreach循环显示出从程序集返回的类型的数组。
- namespace ReflectingAnAssembly
- {
- class Program
- {
- static void Main(string[] args)
- {
- //程序集里面有什么内容
- Assembly a = Assembly.Load("Mscorlib");
- Type[] types = a.GetTypes();
- foreach (Type t in types)
- {
- Console.WriteLine("Type is {0}", t);
- }
- Console.WriteLine("{0} types found", types.Length);
- Console.Read();
- }
- }
- }
反射类型
你也可以通过反射获得Mcsorlib程序集里的某一个类型。要实现这一功能,你可以使用typeOf或GetType()方法从程序集里获取一个类型。
Type theType = Type.GetType("System.Reflection.Assembly");
Console.WriteLine("\nSingle Type is {0}\n", theType);
Console.Read();
输出结果是:
Single Type is System.Reflection.Assembly
查找所用类型成员
你可以使用Type类的GetMembers()方法获得Assembly类型的所用成员,例如下面例子。这个 方法将会列出所有得方法、属性和字段。
- Type theType = Type.GetType("System.Reflection.Assembly");
- Console.WriteLine("\nSingle Type is {0}\n", theType);
- //获得所有成员
- MemberInfo[] mbrInfoArray = theType.GetMembers();
- foreach (MemberInfo mbrInfo in mbrInfoArray)
- {
- Console.WriteLine("{0} is a {1}", mbrInfo, mbrInfo.MemberType);
- }
- Console.Read();
输出的结果很长,但是在输出的结果中,你会看到字段、方法、构造函数和属性。
查找类型的方法
你可能会希望只关注于方法,而排除字段、属性等其他类型的信息。要实现着以目标,你可以去掉对GetMembers()方法的调用。
MemberInfo[] mbrInfoArray = theType.GetMembers();
然后添加对GetMethods()方法的调用: mbrINnfoArray = theType.GetMethods();
现在输出的结果中只包含方法:
查找特定的类型成员
最后如果你希望进一步缩小范围,你可以使用FindMembers方法找到类型的特定成员。例如,你可以将 搜索范围缩小到只包括名称以"Get"开头的方法。
要缩小搜索范围,你须要使用FindMembers方法,这个方法接受4个参数: