反射 在程序集中可以包含多个模块,而每个模块中又可以包含类、接口、委托等类型,在每个类型中又因为类型的不同包含不同的组成部分,比如类(class)中又可以包含字段、属性、方法、实现的接口、继承的父类等信息。字段又可以有类型,名称,是否是静态的等性质,方法中有参数,返回类型等信息。这些信息对于程序的编制人员来说没有什么神秘的,因为这些东西都是程序员自己实现的。可是对于使用这些材料的另外的用户来说,就显得十分神秘,因为这些东西对于使用者来说都是透明的。那么现在能不能有一种方法,得到这些信息呢? 答案是肯定的,因为在程序集中还有一部分元数据——类型元数据。这些类型元数据描述了对应代码中定义的类型和方法等信息。使用.net框架中的反射就可以得到你想要的所有东西。 反射是.net框架中很值得注意的一项功能。通过使用反射,程序可以收集和操纵它自己的原数据。这是一种可以察看程序内部的程序集和对象集合的强有力的机制。反射所需要的类包含在System.Reflection名称空间里。通过反射程序员可以收集和检查某一个对象的类型、属性、方法和事件的信息,并且可以创建这些类型的实例,还可以调用这些类型中的方法。使用反射还可以在内存中创建动态的程序集并执行程序集中的方法,执行完成后你还可以决定是否将动态创建的程序集保存。 反射具有强大的功能,同时也是一个非常庞大的话题。牵扯到的知识点也很多,包括程序集、自定义特性、泛型等,想要完全掌握它非常不易。本节仅仅对反射做一个概要介绍,关于它更精深的内容,需要在实践中逐渐掌握。 简单来说,反射提供这样几个能力: 1、查看和遍历类型(及其成员)的基本信息和程序集元数据(metadata); 2、迟绑定(Late-Binding)方法和属性。 3、创建类型实例(并可以动态调用所创建的实例的方法、字段、属性)。 4、在内存中动态创建程序集,并执行。 11.2.1 反射与System.Type类 System.Type类对于反射起着核心的作用。可以说Type为反射功能的根,也是访问元数据的主要方式。当请求反射某个类型时,就会生成一个Type类的对象,在Type对象中就封装了关于反射类型对象的信息。Type对象就像一个入口,进入这个入口就可以得到反射类型对象的信息,如构造函数、方法、字段、属性 (Property) 和类的事件,以及在其中部署该类的模块和程序集。所以反射的第一步就是获取关于反射类型的Type对象或者实例。有了这个实例就可以使用Type提供的属性和方法获取这个类型的一切信息(方法、字段、属性、事件、参数、构造函数等)。 获取Type对象有两种形式,一种是获取当前加载程序集中的类型(Runtime),一种是获取没有加载的程序集的类型。 先考虑Runtime时的Type,一般来说有三种获取方法: 1. 使用Type类提供的静态方法GetType() 比如想要获得Stream类型的Type实例,则可以这样: Type t = Type.GetType("System.IO.Stream"); txtOutput.Text = t.ToString(); 注意到GetType方法接受字符串形式的类型名称。该名称需要使用一个类型的全名。 2. 使用 typeof 操作符 也可以使用C# 提供的typeof 操作符来完成这一过程: // 如果在程序文件前面使用了using System.IO; 也可以直接用typeof(Stream); Type t = typeof(System.IO.Stream); 这时typeof的参数是一个类型,而不是一个字符串,Stream类型被传递到typeof操作符中。 3. 通过类型实例获得Type对象 可以通过类型的实例来获得: String name = "this is a String"; Type t = name.GetType(); 使用这种方法时应当注意,尽管是通过对象去获取Type对象,但是Type对象不包含关于这个特定对象的信息,仍是保存对象的类型(String)的信息。 到现在为止,已经多次提过Type封装了类型的信息,那么这些类型信息都包含什么内容呢?假设现在有一个类型的实例,它的名字叫做objdemo,对它的信息一无所知,并通过下面代码获取了对于它的Type实例: // 前面某处的代码实例化了objdemo对象 Type t = objdemo.GetType(); 现在,t包含了关于objdemo的哪些信息呢? 1. objdemo类型的基本信息 当然首先想知道objdemo是什么类型的,也就是objdemo的类型名称;还想知道该类型位于什么命名空间下;它的基类型是什么;以及它在.Net运行库中的映射类型;它是值类型还是引用类型;它是不是Public的;它是枚举、是类、是数组、还是接口。它是不是基础类型(int等)。等等。 表11-3列出了Type提供的一些常用属性,用于获取类型的基本信息。 表11-3
2. objdemo类型的成员信息 接着还想知道它有哪些字段;有些什么属性,以及关于这些属性的信息;有哪些构造函数;有哪些方法,方法有哪些参数,有什么样的返回值;包含哪些事件;实现了哪些接口;还可以不加区分地获得它的所有前面列出的成员。 就拿第一个来说,想获取类型都有哪些字段,以及这些字段的信息。而字段都包含哪些信息呢?可能有字段的类型、字段的名称、字段是否public、字段是否为const、字段是否是static等等,那么是不是应该将字段的这些信息也封装起来呢? 实际上,.Net中提供了FiledInfo类型,它封装了关于字段的相关信息。按照前面说列举的成员,类似的还有PropertyInfo类型、ConstructorInfo类型、MethodInfo类型、EventInfo类型。而对于方法而言,对于它的参数,也会有in参数,out参数,参数类型等信息,类似的,在 System.Reflection 命名空间下,除了有上面的提到的那么多Info后缀结尾的类型,还有个ParameterInfo类型,用于封装方法的参数信息。 最后,应该注意到Type类型,以及所有的Info类型均继承自MemberInfo类型(成员信息类型,包含字段、方法、构造函数等所有的成员的类型),MemberInfo类型提供了获取类型基础信息的能力。 在VS2008中键入Type,选中它,再按下F12跳转到Type类型的定义,纵览Type类型的成员,发现可以大致将属性和方法分成这样几组: IsXXXX,比如 IsAbstract,这组bool属性用于判断类型是否属于某个类型,表11-3已经列举了一些。 GetXXXX(),比如GetField(),返回FieldInfo,这组方法用于获取某个成员的信息。 GetXXXXs(),比如GetFields(),返回FieldInfo的一个数组,这组方法让用户获取某类成员信息。 由于MemberInfo是一个基类,当获得一个MemberInfo后,我们并不知道它是PropertyInfo(封装了属性信息的对象)还是FieldInfo(封装了属性信息的对象),所以,有必要提供一个办法来加以判断,在Reflection命名空间中,使用位标记来进行判断,这里先介绍第一个位标记MemberTypes,它用于标记成员类型,可能的取值如下: [Flags] public enum MemberTypes { Constructor = 1, // 该成员是一个构造函数 Event = 2, // 该成员是一个事件 Field = 4, // 该成员是一个字段 Method = 8, // 该成员是一个方法 Property = 16, // 该成员是一个属性 TypeInfo = 32, // 该成员是一种类型 Custom = 64, // 自定义成员类型 NestedType = 128, // 该成员是一个嵌套类型 All = 191, // 指定所有成员类型。 } 具体的使用方法在后面会逐渐介绍到。 |
|