一、概述
1、通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象
2、反射机制允许程序在执行过程中动态地添加各种功能
二、运行时类型标识
1、运行时类型标志(RTTI),可以在程序执行期间判断对象类型。例如使用他能够确切的知道基类引用指向了什么类型对象。
2、运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常。
3、在C#中有三个支持RTTI的关键字:is、as、typeof。下面一次介绍他们
is运算符:
通过is运算符,能够判断对象类型是否为特定类型,如果两种类型时相同类型,或者两者之间存在引用,装箱拆箱转换,则表明两种类型时兼容的。代码如下:
1 static void Main()
2 {
3 A a = new A();
4 B b = new B();
5 if (a is A)
6 {
7 Console.WriteLine("a is an A");
8 }
9
10 if (b is A)
11 {
12 Console.WriteLine("b is an A because it is derived from");
13 }
14
15 if (a is B)
16 {
17 Console.WriteLine("This won't display,because a not derived from B");
18 }
19
20 if (a is object)
21 {
22 Console.WriteLine("a is an object");
23 }
24 Console.ReadKey();
25 }
结果:
as运算符:
在运行期间执行类型转换,并且能够是的类型转换失败不抛出异常,而返回一个null值,其实as也可以看作一个is运算符的简化备选方式,如下:
1 static void Main()
2 {
3 A a = new A();
4 B b = new B();
5 if (a is B)
6 {
7 b = (B) a;//由于a变量不是B类型,因此这里将a变量转换为B类型时无效的
8 }
9 else
10 {
11 b = null;
12 }
13
14 if (b==null)
15 {
16 Console.WriteLine("The cast in b=(B)a is not allowed");
17 }
18 //上面使用as运算符,能够把两部分合二为一
19 b = a as B;//as运算符先检查将之转换类型的有效性,如果有效,则执行强类型转换过程,这些都在这一句话完成
20 if (b==null)
21 {
22 Console.WriteLine("The cast in b=(B)a is not allowed");
23 }
24 Console.ReadKey();
25 }
结果:
typeof运算符:
as、is 能够测试两种类型的兼容性,但大多数情况下,还需要获得某个类型的具体信息。这就用到了typeof,他可以返回与具体类型相关的System.Type对象,通过System.Type对象可以去定此类型的特征。一旦获得给定类型的Type对象,就可以通过使用对象定义的各自属性、字段、方法来获取类型的具体信息。Type类包含了很多成元,在接下来的反射中再详细讨论。下面简单的演示Type对象,调用它的三个属性。
1 static void Main()
2 {
3 Type t = typeof(StringBuilder);
4 Console.WriteLine(t.FullName);//FullName属性返回类型的全称
5 if (t.IsClass)
6 {
7 Console.WriteLine("is a Class");
8 }
9
10 if (t.IsSealed)
11 {
12 Console.WriteLine("is Sealed");
13 }
14 Console.ReadKey();
15 }
结果:
三、反射的核心类型:System.Type类
1、许多支持反射的类型都位于System.Reflection命名空间中,他们是.net Reflection API的一部分,所以再使用的反射的程序中一般都是要使用System.Reflection的命名空间。
2、System.Type类包装了类型,因此是整个反射子系统的核心,这个类中包含了很多属性和方法,使用这些属性和方法可以再运行时得到类型的信息。
3、Type类派生于System.Reflection.MemberInfo抽象类
MemberInfo类中的只读属性 |
|
属性 |
描述 |
Type DeclaringType |
获取声明该成员的类或接口的类型 |
MemberTypes MemberType |
获取成员的类型,这个值用于指示该成员是字段、方法、属性、事件、或构造函数 |
Int MetadataToken |
获取与特定元数据相关的值 |
Module Module |
获取一个代表反射类型所在模块(可执行文件)的Module对象 |
String Name |
成员的名称 |
Type ReflectedType |
反射的对象类型 |
请注意:
1、MemberType属性的返回类型为MemberTypes,这是一个枚举,它定义了用于表示不同成元的信息值,这些包括:MemberTypes.Constructor、MemeberTypes.Method、MemberTypes.Event、MemberTypes.Property。因此可以通过检查MemberType属性来确定成元的类型,例如在MenberType属性的值为MemberTypes.Method时,该成员为方法
2、MemberInfo类还包含两个与特性相关的抽象方法:
(1)GetCustomAttributes():获得与主调对象相关的自定义特性列表。
(2)IsDefined():确定是否为主调对象定义了相应的特性。
(3)GetCustomeAttributesData():返回有关自定义特性的信息(特性稍后便会提到)
当然除了MemberInfo类定义的方法和属性外,Type类自己也添加了许多属性和方法:如下表(只列出一些常用的,太多二零,自己可以转定义Type类看一下)
Type类定义的方法 |
|
方法 |
功能 |
ConstructorInfo[] GetConstructors() |
获取指定类型的构造函数列表 |
EventInfo[] GetEvents(); |
获取指定类型的时间列 |
FieldInfo[] GetFields(); |
获取指定类型的字段列 |
Type[] GetGenericArguments(); |
获取与已构造的泛型类型绑定的类型参数列表,如果指定类型的泛型类型定义,则获得类型形参。对于正早构造的类型,该列表就可能同时包含类型实参和类型形参 |
MemberInfo[] GetMembers(); |
获取指定类型的成员列表 |
MethodInfo[] GetMethods(); |
获取指定类型的方法列表 |
PropertyInfo[] GetProperties(); |
获取指定类型的属性列表 |
下面列出Type类型定义的常用只读属性
Type类定义的属性 |
|
属性 |
功能 |
Assembly Assembly |
获取指定类型的程序集 |
TypeAttributes Attributes |
获取制定类型的特性 |
Type BaseType |
获取指定类型的直接基类型 |
String FullName |
获取指定类型的全名 |
bool IsAbstract |
如果指定类型是抽象类型,返回true |
bool IsClass |
如果指定类型是类,返回true |
string Namespace |
获取指定类型的命名空间 |
四、使用反射
上面将的这些,都是为了使用反射做铺垫的。
通过使用Type类定义的方法和属性,我们能够在运行时获得类型的各种具体信息。这是一个非常强大的功能,我们一旦得到类型信息,就可以调用其构造函数、方法、属性,可见,反射是允许使用编译时不可用的代码的。
由于Feflection API非常多,这里不可能完整的介绍他们(这里如果完整的介绍,据说要一本书,厚书)。但是Reflection API是按照一定逻辑设计的,因此,只要知道部分接口的使用方法,就可以举一反三的使用剩余的接口。
这里我列出四种关键的反射技术:
1、获取方法的信息
2、调用方法
3、构造对象
4、从程序集中加载类型
五、获取方法的相关信息
一旦有了Type对象就可以使用GetMethodInfo()方法获取此类型支持的所有方法列表。该方法返回一个MethodInfo对象数组,MethodInfo对象表述了主调类型所支持的方法,它位于System.Reflection命名空间中。MethodInfo类派生于MethodBase抽象类,而MethodBase类继承了MemberInfo类,因此,我们能够使用这三各类定义的属性和方法。例如,使用Name属性的到方法名,这里有两个重要的成员:
1、ReturnType属性:为Type类型的对象,能够提供方法的返回类型信息。
2、GetParameters()方法:返回参数列表,参数信息以数组的形式保存在PatameterInfo对象中。PatameterInfo类定义了大量表述参数信息的属性和方法,这里也累出两个常用的属性:Name(包含参数名称信息的字符串),ParameterType(参数类型的信息)。
下面代码我将使用反射获得类中的所支持的方法,还有方法的信息:
1 class Program
2 {
3 static void Main()
4 {
5 //获取描述MyClass类型的Type对象
6 Type t = typeof(MyClass);
7 Console.WriteLine($"Analyzing methods in {t.Name}");
8 //MethodInfo对象在System.Reflection命名空间下
9 MethodInfo[] mi = t.GetMethods();
10 foreach (var methodInfo in mi)
11 {
12 //返回方法的返回类型
13 Console.Write(methodInfo.ReturnType.Name);
14 //返回方法的名称
15 Console.Write($" {methodInfo.Name} (");
16 //获取方法阐述列表并保存在ParameterInfo对象组中
17 ParameterInfo[] pi = methodInfo.GetParameters();
18 for (int i = 0; i < pi.Length; i++)
19 {
20 //方法的参数类型名称
21 Console.Write(pi[i].ParameterType.Name);
22 //方法的参数名
23 Console.Write($" {pi[i].Name}");
24 if (i+1<pi.Length)
25 {
26 Console.Write(", ");
27 }
28 }
29
30 Console.Write(")");
31 Console.Write("\r\n");
32 Console.WriteLine("--------------------------");
33 }
34 Console.ReadKey();
35 }
36 }
37
38 class MyClass
39 {
40 private int x;
41 private int y;
42
43 public MyClass()
44 {
45 x = 1;
46 y = 1;
47 }
48
49 public int Sum()
50 {
51 return x + y;
52 }
53
54 public bool IsBetween(int i)
55 {
56 if (x < i && i < y)
57 {
58 return true;
59 }
60
61 return false;
62 }
63
64 public void Set(int a, int b)
65 {
66 x = a;
67 y = b;
68 }
69
70 public void Set(double a, double b)
71 {
72 x = (int)a;
73 y = (int)b;
74 }
75
76 public void Show()
77 {
78 System.Console.WriteLine($"x:{x},y:{y}");
79 }
80 }
输出结果:
注意:这里输出的除了MyClass类定义的所有方法外,也会显示object类定义的共有非静态方法。这是因为C#中的所有类型都继承于Object类。另外,这些信息是在程序运行时动态获得的,并不需要知道MyClass类的定义
GetMethods()方法的另一种形式
这种形式可以指定各种标记,已筛选想要获取的方法,他的通用形式为:MethodInfo[] GetMethods(BindingFlags bindingAttr)
BindingFlags是一个枚举,枚举值有(很多,这里只列出5个常用的吧)
(1)DeclareOnly:仅获取指定类定义的方法,而不获取所继承的方法
(2)Instance:获取实例方法
(3)NonPublic:获取非公有方法
(4)Public:获取共有方法
(5)Static:获取静态方法
GetMethods(BindingFlags bindingAttr)这个方法,参数可以使用 or 把两个或更多标记连接在一起,实际上至少要有Instance(或 Static)与Public(或 NonPublic)标记,否则将不会获取任何方法。下我们就写一个示例来演示一下。
1 class Program
2 {
3 static void Main()
4 {
5 //获取描述MyClass类型的Type对象
6 Type t = typeof(MyClass);
7 Console.WriteLine($"Analyzing methods in {t.Name}");
8 //MethodInfo对象在System.Reflection命名空间下
9 //不获取继承方法,为实例方法,·为公用的
10 MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Public);
11 foreach (var methodInfo in mi)
12 {
13 //返回方法的返回类型
14 Console.Write(methodInfo.ReturnType.Name);
15 //返回方法的名称
16 Console.Write($" {methodInfo.Name} (");
17 //获取方法阐述列表并保存在ParameterInfo对象组中
18 ParameterInfo[] pi = methodInfo.GetParameters();
19 for (int i = 0; i < pi.Length; i++)
20