.net 反射2

来源:互联网 发布:药店用什么软件 编辑:程序博客网 时间:2024/05/05 10:22

.Net 中的反射(查看基本类型信息) - Part.2

反射概述 和Type类

1.反射的作用

简单来说,反射提供这样几个能力:1、查看和遍历类型(及其成员)的基本信息和程序集元数据(metadata);2、迟绑定(Late-Binding)方法和属性。3、动态创建类型实例(并可以动态调用所创建的实例的方法、字段、属性)。序章中,我们所采用的那个例子,只是反射的一个用途:查看类型成员信息。接下来的几个章节,我们将依次介绍反射所提供的其他能力。

2.获取Type对象实例

反射的核心是Type类,这个类封装了关于对象的信息,也是进行反射的入口。当你获得了关于类型的Type对象后,就可以根据Type提供的属性和方法获取这个类型的一切信息(方法、字段、属性、事件、参数、构造函数等)。我们开始的第一步,就是获取关于类型的Type实例。获取Type对象有两种形式,一种是获取当前加载程序集中的类型(Runtime),一种是获取没有加载的程序集的类型。

我们先考虑Runtime时的Type,一般来说有三种获取方法:

2.1使用Type类提供的静态方法GetType()

比如我们想要获得Stream类型的Type实例,则可以这样:

Type t = Type.GetType("System.IO.Stream");
txtOutput.Text = t.ToString();

注意到GetType方法接受字符串形式的类型名称。

2.2 使用 typeof 操作符

也可以使用C# 提供的typeof 操作符来完成这一过程:

// 如果在页首写入了using System.IO; 也可以直接用 typeof(Stream);
Type t = typeof(System.IO.Stream);

这时的使用有点像泛型,Stream就好像一个类型参数一样,传递到typeof操作符中。

2.3 通过类型实例获得Type对象

我们还可以通过类型的实例来获得:

String name = "Jimmy Zhang";
Type t = name.GetType();

使用这种方法时应当注意,尽管我们是通过变量(实例)去获取Type对象,但是Type对象不包含关于这个特定对象的信息,仍是保存对象的类型(String)的信息。

3.Type类型 及 Reflection命名空间的组织结构

到现在为止,我已经多次提过Type封装了类型的信息,那么这些类型信息都包含什么内容呢?假设我们现在有一个类型的实例,它的名字叫做 demo,我们对它的信息一无所知,并通过下面代码获取了对于它的Type实例:

// 前面某处的代码实例化了demo对象
Type t = demo.GetType();

现在,我们期望 t 包含了关于 demo 的哪些信息呢?

3.1 demo的类型的基本信息

  • 我们当然首先想知道 demo 是什么类型的,也就是 demo 的类型名称。
  • 我们还想知道该类型位于什么命名空间下。
  • 它的基类型是什么,以及它在.Net运行库中的映射类型。
  • 它是值类型还是引用类型。
  • 它是不是Public的。
  • 它是枚举、是类、是数组、还是接口。
  • 它是不是基础类型(int等)。
  • 等等 ...

Type 提供了下面的属性,用于获取类型的基本信息,常用的有下面一些:

属 性说 明Name获取类型名称FullName类型全名Namespace命名空间名称BaseType获取对于基类的Type类型的引用UnderlyingSystemType在.Net中映射的类型的引用Attributes获取TypeAttributes位标记IsValueType是否值类型IsByRef是否由引用传递IsEnum是否枚举IsClass是否类IsInterface是否接口IsSealed是否密封类IsPrimitive是否基类型(比如int)IsAbstract是否抽象IsPublic是否公开IsNotPublic是否非公开IsVisible是否程序集可见等等...  

3.2 demon的类型的成员信息  

  • 我们可能还想知道它有哪些字段。
  • 有些什么属性,以及关于这些属性的信息。
  • 有哪些构造函数。
  • 有哪些方法,方法有哪些参数,有什么样的返回值。
  • 包含哪些事件。
  • 实现了哪些接口。
  • 我们还可以不加区分地获得它的所有 以上成员。

观察上面的列表,就拿第一条来说,我们想获取类型都有哪些字段,以及这些字段的信息。而字段都包含哪些信息呢?可能有字段的类型、字段的名称、字段是否public、字段是否为const、字段是否是read only 等等,那么是不是应该将字段的这些信息也封装起来呢?

实际上,.Net中提供了 FiledInfo 类型,它封装了关于字段的相关信息。对照上面的列表,类似的还有 PropertyInfo类型、ConstructorInfo类型、MethodInfo类型、EventInfo类型。而对于方法而言,对于它的参数,也会有in参数,out参数,参数类型等信息,类似的,在 System.Reflection 命名空间下,除了有上面的提到的那么多Info后缀结尾的类型,还有个ParameterInfo 类型,用于封装方法的参数信息。

最后,应该注意到 Type 类型,以及所有的Info类型均 继承自 MemberInfo 类型,MemberInfo类型提供了获取类型基础信息的能力。

在VS2005中键入Type,选中它,再按下F12跳转到Type类型的定义,纵览Type类型的成员,发现可以大致将属性和方法分成这样几组:

  • IsXXXX,比如 IsAbstract,这组bool属性用于说明类型的某个信息。(前面的表格已经列举了一些。)
  • GetXXXX(),比如GetField(),返回FieldInfo,这组方法用于获取某个成员的信息。
  • GetXXXXs(),比如GetFields(),返回FieldInfo[],这组方法用户获取某些成员信息。
  • 还有其他的一些属性和方法,等后面遇到了再说。

由于MemberInfo是一个基类,当我们获得一个MemberInfo后,我们并不知道它是PropertyInfo(封装了属性信息的对象)还是FieldInfo(封装了属性信息的对象),所以,有必要提供一个办法可以让我们加以判断,在Reflection 命名空间中,会遇到很多的位标记,这里先介绍第一个位标记(本文管用[Flags]特性标记的枚举称为 位标记),MemberTypes,它用于标记成员类型,可能的取值如下:

[Flags]
public enum MemberTypes {
    Constructor = 1,  //  该成员是一个构造函数
    Event = 2,        //  该成员是一个事件
    Field = 4,        //  该成员是一个字段
    Method = 8,           //  该成员是一个方法
    Property = 16,    //  该成员是一个属性
    TypeInfo = 32,    //  该成员是一种类型
    Custom = 64,      //  自定义成员类型
    NestedType = 128, //  该成员是一个嵌套类型
    All = 191,        //  指定所有成员类型。
}

反射程序集

在.Net中,程序集是进行部署、版本控制的基本单位,它包含了相关的模块和类型,我并不打算详细地去说明程序集及其构成,只是讲述如何通过反射获取程序集信息。

在System.Reflection命名空间下有一个Assembly类型,它代表了一个程序集,并包含了关于程序集的信息。

在程序中加载程序集时,一般有这么几个方法,我们可以使用 Assembly类型提供的静态方法LoadFrom() 和 Load(),比如:

Assembly asm = Assembly.LoadFrom("Demo.dll");

或者

Assembly asm = Assembly.Load("Demo");

当使用LoadFrom()方法的时候,提供的是程序集的文件名,当将一个程序集添加到项目引用中以后,可以直接写“文件名.dll”。如果想加载一个不属于当前项目的程序集,则需要给出全路径,比如:

Assembly asm = Assembly.LoadFrom(@"C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/System.Web.dll");

使用Load()方法的时候,只用提供程序集名称即可,不需要提供程序集的后缀名。如果想获得当前程序集,可以使用Assembly类型的静态方法 GetExecutingAssembly,它返回包含当前执行的代码的程序集(也就是当前程序集)。

Assembly as = Assembly.GetExecutingAssembly();

在获得一个Type类型实例以后,我们还可以使用该实例的Assembly属性来获得其所在的程序集:

Type t = typeof(int)
Assembly asm = t.Assembly;

一个程序集可能有多个模块(Module)组成,每个模块又可能包含很多的类型,但.Net的默认编译模式一个程序集只会包含一个模块,我们现在看下 反射 提供了什么样的能力让我们获取关于程序集的信息(只列出了部分常用的):

属 性/方 法说 明FullName程序集名称Location程序集的路径GetTypes()获取程序集包含的全部类型GetType()获取某个类型GetModules()获取程序集包含的模块GetModule()获取某个模块GetCustomAttributes()获取自定义特性信息

NOTE:程序集和命名空间不存在必然联系,一个程序集可以包含多个命名空间,同一个命名空间也可以分放在几个程序集。

为了方便进行我们后面的测试,我们现在建立一个Windows控制台应用程序,我给它起名叫SimpleExplore;然后再添加一个Demo类库项目,我们将来编写的代码就用户查看这个Demo项目集的类型信息 或者 是对这个程序集中的类型进行迟绑定。这个Demon项目只包含一个命名空间Demo,为了体现尽可能多的类型同时又Keep Simple,其代码如下:

namespace Demo {

    public abstract class BaseClass {
      
    }

    public struct DemoStruct { }

    public delegate void DemoDelegate(Object sender, EventArgs e);

    public enum DemoEnum {
       terrible, bad, common=4, good, wonderful=8
    }

    public interface IDemoInterface {
       void SayGreeting(string name);     
    }

    public interface IDemoInterface2 {}
   
    public sealed class DemoClass:BaseClass, IDemoInterface,IDemoInterface2 {

       private string name;
       public string city;
       public readonly string title;
       public const string text = "Const Field";
       public event DemoDelegate myEvent;     
             
       public string Name {
           private get { return name; }
           set { name = value; }
       }

       public DemoClass() {
           title = "Readonly Field";
       }

       public class NestedClass { }

       public void SayGreeting(string name) {
           Console.WriteLine("Morning :" + name);
       }
    }

}

现在我们在 SimpleExplore项目中写一个方法AssemblyExplor(),查看我们Demo项目生成的程序集Demo.dll定义的全部类型:

public static void AssemblyExplore() {
    StringBuilder sb = new StringBuilder();

    Assembly asm = Assembly.Load("Demo");

    sb.Append("FullName(全名):" + asm.FullName + "/n");
    sb.Append("Location(路径):" + asm.Location + "/n");

    Type[] types = asm.GetTypes();

    foreach (Type t in types) {
       sb.Append("   类型:" + t + "/n");
    }

    Console.WriteLine(sb.ToString());
}

然后,我们在Main()方法中调用一下,应该可以看到这样的输出结果:

FullName(全名):Demo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Location(路径):E:/MyApp/TypeExplorer/SimpleExplorer/bin/Debug/Demo.dll
模块: Demo.dll
   类型:Demo.BaseClass
   类型:Demo.DemoStruct
   类型:Demo.DemoDelegate
   类型:Demo.DemoEnum
   类型:Demo.IDemoInterface
   类型:Demo.IDemoInterface2
   类型:Demo.DemoClass
   类型:Demo.DemoClass+NestedClass

反射基本类型

这里说反射基本类型,基本类型是针对 泛型类型 来说的,因为 反射泛型 会更加复杂一些。在前面的范例中,我们获得了程序集中的所有类型,并循环打印了它们,打印结果仅仅显示出了类型的全名,而我们通常需要关于类型更详细的信息,本节我们就来看看如何进一步查看类型信息。

NOTE:因为一个程序集包含很多类型,一个类型包含很多成员(方法、属性等),一个成员又包含很多其他的信息,所以如果我们从程序集层次开始写代码去获取每个层级的信息,那么会嵌套很多的foreach语句,为了阅读方便,我会去掉最外层的循环。

1.获取基本信息

有了前面Type一节的介绍,我想完成这里应该只是打打字而已,所以我直接写出代码,如有必要,会在注释中加以说明。我们再写一个方法TypeExplore,用于获取类型的详细信息(记得AssemblyExplore只获取了类型的名称):

public static void TypeExplore(Type t) {
    StringBuilder sb = new StringBuilder();

    sb.Append("名称信息:/n");
    sb.Append("Name: " + t.Name + "/n");
    sb.Append("FullName: " + t.FullName + "/n");
    sb.Append("Namespace: " + t.Namespace + "/n");

    sb.Append("/n其他信息:/n");
    sb.Append("BaseType(基类型): " + t.BaseType + "/n");
    sb.Append("UnderlyingSystemType: " + t.UnderlyingSystemType + "/n");

    sb.Append("/n类型信息:/n");
    sb.Append("Attributes(TypeAttributes位标记): " + t.Attributes + "/n");
    sb.Append("IsValueType(值类型): " + t.IsValueType + "/n");
    sb.Append("IsEnum(枚举): " + t.IsEnum + "/n");
    sb.Append("IsClass(类): " + t.IsClass + "/n");
    sb.Append("IsArray(数组): " + t.IsArray + "/n");
    sb.Append("IsInterface(接口): " + t.IsInterface + "/n");
    sb.Append("IsPointer(指针): " + t.IsPointer + "/n");
    sb.Append("IsSealed(密封): " + t.IsSealed + "/n");
    sb.Append("IsPrimitive(基类型): " + t.IsPrimitive + "/n");
    sb.Append("IsAbstract(抽象): " + t.IsAbstract + "/n");
    sb.Append("IsPublic(公开): " + t.IsPublic + "/n");
    sb.Append("IsNotPublic(不公开): " + t.IsNotPublic + "/n");
    sb.Append("IsVisible: " + t.IsVisible + "/n");
    sb.Append("IsByRef(由引用传递): " + t.IsByRef + "/n");

    Console.WriteLine(sb.ToString());
}

然后,我们在Main方法中输入:

Type t = typeof(DemoClass);
TypeExplore(t);

会得到这样的输出:

名称信息:
Name: DemoClass
FullName: Demo.DemoClass
Namespace: Demo

其他信息:
BaseType(基类型): Demo.BaseClass
UnderlyingSystemType: Demo.DemoClass

类型信息:
Attributes(TypeAttributes位标记): AutoLayout, AnsiClass, Class, Public, Sealed,
BeforeFieldInit
IsValueType(值类型): False
IsEnum(枚举): False
IsClass(类): True
IsArray(数组): False
IsInterface(接口): False
IsPointer(指针): False
IsSealed(密封): True
IsPrimitive(基类型): False
IsAbstract(抽象): False
IsPublic(公开): True
IsNotPublic(不公开): False
IsVisible: True
IsByRef(由引用传递): False

值得注意的是Attributes属性,它返回一个TypeAttributes位标记,这个标记标识了类型的一些元信息,可以看到我们熟悉的Class、Public、Sealed。相应的,IsClass、IsSealed、IsPublic等属性也返回为True。

2.成员信息 与 MemberInfo 类型

我们先考虑一下对于一个类型Type,可能会包含什么类型,常见的有字段、属性、方法、构造函数、接口、嵌套类型等。MemberInfo 类代表着 Type的成员类型,值得注意的是Type类本身又继承自MemberInfo类,理解起来并不困难,因为一个类型经常也是另一类型的成员。Type类提供 GetMembers()、GetMember()、FindMember()等方法用于获取某个成员类型。

我们再添加一个方法 MemberExplore(),来查看一个类型的所有成员类型。

public static void MemberExplore(Type t) {
    StringBuilder sb = new StringBuilder();

    MemberInfo[] memberInfo = t.GetMembers();

    sb.Append("查看类型 " + t.Name + "的成员信息:/n");

    foreach (MemberInfo mi in memberInfo) {
       sb.Append("成员:" + mi.ToString().PadRight(40) + " 类型: " + mi.MemberType + "/n");
    }

    Console.WriteLine(sb.ToString());
}

然后我们在Main方法中调用一下。

MemberExplore(typeof(DemoClass));

产生的输出如下:

查看类型 DemoClass的成员信息:
--------------------------------------------------
成员:Void add_myEvent(Demo.DemoDelegate)      类型: Method
成员:Void remove_myEvent(Demo.DemoDelegate)   类型: Method
成员:System.String get_Name()                 类型: Method
成员:Void set_Name(System.String)             类型: Method
成员:Void SayGreeting(System.String)          类型: Method
成员:System.Type GetType()                    类型: Method
成员:System.String ToString()                 类型: Method
成员:Boolean Equals(System.Object)            类型: Method
成员:Int32 GetHashCode()                      类型: Method
成员:Void .ctor()                             类型: Constructor
成员:System.String Name                       类型: Property
成员:Demo.DemoDelegate myEvent                类型: Event
成员:System.String text                       类型: Field
成员:Demo.DemoClass+NestedClass               类型: NestedType

我们使用了GetMembers()方法获取了成员信息的一个数组,然后遍历了数组,打印了成员的名称和类型。如同我们所知道的:Name属性在编译后成为了get_Name()和set_Name()两个独立的方法;myEvent事件的注册(+=)和取消注册(-=)分别成为了add_myEvent()和remove_myEvent方法。同时,我们发现私有(private)字段name 没有被打印出来,另外,基类System.Object的成员GetType()和Equals()也被打印了出来。

有的时候,我们可能不希望查看基类的成员,也可能希望查看私有的成员,此时可以使用GetMembers()的重载方法,传入BindingFlags 位标记参数来完成。BindingFlags位标记对如何获取成员的方式进行控制(也可以控制如何创建对象实例,后面会说明)。对于本例,如果我们想获取所有的公有、私有、静态、实例 成员,那么只需要这样修改GetMembers()方法就可以了。

MemberInfo[] memberInfo = t.GetMembers(
    BindingFlags.Public |
    BindingFlags.Static |
    BindingFlags.NonPublic |
    BindingFlags.Instance |
    BindingFlags.DeclaredOnly
);

此时的输出如下:

查看类型 DemoClass的成员信息:
--------------------------------------------------
成员:Void add_myEvent(Demo.DemoDelegate)      类型: Method
成员:Void remove_myEvent(Demo.DemoDelegate)   类型: Method
成员:System.String get_Name()                 类型: Method
成员:Void set_Name(System.String)             类型: Method
成员:Void SayGreeting(System.String)          类型: Method
成员:Void .ctor()                             类型: Constructor
成员:System.String Name                       类型: Property
成员:Demo.DemoDelegate myEvent                类型: Event
成员:System.String name                       类型: Field
成员:Demo.DemoDelegate myEvent                类型: Field
成员:System.String text                       类型: Field
成员:Demo.DemoClass+NestedClass               类型: NestedType

可以看到,继承自基类 System.Object 的方法都被过滤掉了,同时,打印出了私有的 name, myEvent 等字段。

现在如果我们想要获取所有的方法(Method),那么我们可以使用 Type类的FindMembers()方法:

MemberInfo[] memberInfo = t.FindMembers(
    MemberTypes.Method,      // 说明查找的成员类型为 Method
    BindingFlags.Public |
    BindingFlags.Static |
    BindingFlags.NonPublic |
    BindingFlags.Instance |
    BindingFlags.DeclaredOnly,
    Type.FilterName,
    "*"
);

Type.FilterName 返回一个MemberFilter类型的委托,它说明按照方法名称进行过滤,最后一个参数“*”,说明返回所有名称(如果使用“Get*”,则会返回所有以Get开头的方法)。现在的输出如下:

查看类型 DemoClass的成员信息:
--------------------------------------------------
成员:Void add_myEvent(Demo.DemoDelegate)      类型: Method
成员:Void remove_myEvent(Demo.DemoDelegate)   类型: Method
成员:System.String get_Name()                 类型: Method
成员:Void set_Name(System.String)             类型: Method
成员:Void SayGreeting(System.String)          类型: Method

MemberInfo 类有两个属性值得注意,一个是DeclaringType,一个是 ReflectedType,返回的都是Type类型。DeclaredType 返回的是声明该成员的类型。比如说,回顾我们之前的一段代码:

MemberInfo[] members = typeof(DemoClass).GetMembers();

它将返回所有的公有成员,包括继承自基类的Equals()等方法,对于Equals()方法来说,它的 DeclaringType 返回的是相当于 typeof(Object) 的类型实例,因为它是在 System.Object中被定义的;而它的ReflectedType 返回的则是相当于 typeof(DemoClass) 类型实例,因为它是通过 DemoClass 的类型实例被获取的。

3.字段信息 与 FieldInfo类型

如同我们之前所说,MemberInfo 是一个基类,它包含的是类型的各种成员都公有的一组信息。实际上,对于字段、属性、方法、事件 等类型成员来说,它们包含的信息显然都是不一样的,所以,.Net 中提供了 FiledInfo 类型来封装字段的信息,它继承自MemberInfo。

如果我们希望获取一个类型的所有字段,可以使用 GetFileds()方法。我们再次添加一个方法FieldExplore():

public static void FieldExplore(Type t) {
    StringBuilder sb = new StringBuilder();
   
    FieldInfo[] fields = t.GetFields();

    sb.Append("查看类型 " + t.Name + "的字段信息:/n");
    sb.Append(String.Empty.PadLeft(50, '-') + "/n");

    foreach (FieldInfo fi in fields) {
       sb.Append("名称:" + fi.Name + "/n");
       sb.Append("类型:" + fi.FieldType + "/n");
       sb.Append("属性:" + fi.Attributes + "/n/n");
    }

    Console.WriteLine(sb.ToString());
}

产生的输出如下:

查看类型 DemoClass的字段信息:
--------------------------------------------------
名称:city
类型:System.String
属性:Public

名称:title
类型:System.String
属性:Public, InitOnly

名称:text
类型:System.String
属性:Public, Static, Literal, HasDefault

值得一提的是fi.FieldType 属性,它返回一个FieldAttributes位标记,这个位标记包含了字段的属性信息。对比我们之前定义的DemoClass类,可以看到,对于title 字段,它的属性是public, InitOnly;对于Const类型的text字段,它的属性为Public,Static,Literal,HasDefault,由此也可以看出,声明一个const类型的变量,它默认就是静态static的,同时,由于我们给了它初始值,所以位标记中也包括HasDefault。

针对于FieldType位标记,FiledInfo 类提供了一组返回为bool类型的属性,来说明字段的信息,常用的有:IsPublic, IsStatic, IsInitOnly, IsLiteral, IsPrivate 等。

如果我们想要获取私有字段信息,依然可以使用重载了的GetFields[]方法,传入BindingFlags参数,和上面的类似,这里就不重复了。

4.属性信息 与 PropertyInfo 类型

和字段类似,也可以通过 GetProperty()方法,获取类型的所有属性信息。

public static void PropertyExplore(Type t) {
    StringBuilder sb = new StringBuilder();
    sb.Append("查看类型 " + t.Name + "的属性信息:/n");
    sb.Append(String.Empty.PadLeft(50, '-') + "/n");

    PropertyInfo[] properties = t.GetProperties();

    foreach (PropertyInfo pi in properties) {
       sb.Append("名称:" + pi.Name + "/n");
       sb.Append("类型:" + pi.PropertyType + "/n");
       sb.Append("可读:" + pi.CanRead + "/n");
       sb.Append("可写:" + pi.CanWrite +"/n");
       sb.Append("属性:" + pi.Attributes +"/n");
    }

    Console.WriteLine(sb.ToString());
}

输出如下:

查看类型 DemoClass的属性信息:
--------------------------------------------------
名称:Name
类型:System.String
可读:True
可写:True
属性:None

从前面的章节可以看到,Name属性会在编译后生成Get_Name()和Set_Name()两个方法,那么,应该可以利用反射获取这两个方法。PropertyInfo类的GetGetMethod()和GetSetMethod()可以完成这个工作,它返回一个MethodInfo对象,封装了关于方法的信息,我们会在后面看到。

5.方法信息 与 MethodInfo 类型

与前面的类似,我们依然可以编写代码来查看类型的方法信息。

public static void MethodExplore(Type t) {
    StringBuilder sb = new StringBuilder();
    sb.Append("查看类型 " + t.Name + "的方法信息:/n");
    sb.Append(String.Empty.PadLeft(50, '-') + "/n");

    MethodInfo[] methods = t.GetMethods();

    foreach (MethodInfo method in methods) {
       sb.Append("名称:" + method.Name +"/n");
       sb.Append("签名:" + method.ToString() + "/n");
       sb.Append("属性:" + method.Attributes + "/n");
       sb.Append("返回值类型:" + method.ReturnType + "/n/n");
    }

    Console.WriteLine(sb.ToString());
}

与前面类似,MethodInfo 类也有一个Attributes属性,它返回一个MethodAttribute,MethodAttribute 位标记标明了方法的一些属性,常见的比如Abstract, Static, Virtual,Public, Private 等。

与前面不同的是,Method可以具有参数 和 返回值,MethodInfo 类提供了 GetParameters() 方法获取 参数对象的数组,方法的参数都封装在了 ParameterInfo 类型中。查看ParameterInfo类型的方法与前面类似,这里就不再阐述了。

4. ConstructorInfo类型、EventInfo 类型

从名称就可以看出来,这两个类型封装了类型 的构造函数 和 事件信息,大家都是聪明人,查看这些类型与之前的方法类似,这里就不再重复了。

5.小结

本文涉及了反射的最基础的内容,我们可以利用反射来自顶向下地查看程序集、模块、类型、类型成员的信息。反射更强大、也更有意思的内容:迟绑定方法、动态创建类型以后会再讲到。