一、概述 1、通過反射可以提供類型信息,從而使得我們開發(fā)人員在運(yùn)行時能夠利用這些信息構(gòu)造和使用對象 2、反射機(jī)制允許程序在執(zhí)行過程中動態(tài)地添加各種功能 二、運(yùn)行時類型標(biāo)識 1、運(yùn)行時類型標(biāo)志(RTTI),可以在程序執(zhí)行期間判斷對象類型。例如使用他能夠確切的知道基類引用指向了什么類型對象。 2、運(yùn)行時類型標(biāo)識,能預(yù)先測試某個強(qiáng)制類型轉(zhuǎn)換操作,能否成功,從而避免無效的強(qiáng)制類型轉(zhuǎn)換異常。 3、在C#中有三個支持RTTI的關(guān)鍵字:is、as、typeof。下面一次介紹他們 is運(yùn)算符: 通過is運(yùn)算符,能夠判斷對象類型是否為特定類型,如果兩種類型時相同類型,或者兩者之間存在引用,裝箱拆箱轉(zhuǎn)換,則表明兩種類型時兼容的。代碼如下: 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 }
結(jié)果:
as運(yùn)算符: 在運(yùn)行期間執(zhí)行類型轉(zhuǎn)換,并且能夠是的類型轉(zhuǎn)換失敗不拋出異常,而返回一個null值,其實(shí)as也可以看作一個is運(yùn)算符的簡化備選方式,如下:
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變量轉(zhuǎn)換為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運(yùn)算符,能夠把兩部分合二為一
19 b = a as B;//as運(yùn)算符先檢查將之轉(zhuǎn)換類型的有效性,如果有效,則執(zhí)行強(qiáng)類型轉(zhuǎn)換過程,這些都在這一句話完成
20 if (b==null)
21 {
22 Console.WriteLine("The cast in b=(B)a is not allowed");
23 }
24 Console.ReadKey();
25 }
結(jié)果:
typeof運(yùn)算符: as、is 能夠測試兩種類型的兼容性,但大多數(shù)情況下,還需要獲得某個類型的具體信息。這就用到了typeof,他可以返回與具體類型相關(guān)的System.Type對象,通過System.Type對象可以去定此類型的特征。一旦獲得給定類型的Type對象,就可以通過使用對象定義的各自屬性、字段、方法來獲取類型的具體信息。Type類包含了很多成元,在接下來的反射中再詳細(xì)討論。下面簡單的演示Type對象,調(diào)用它的三個屬性。
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 }
結(jié)果:
三、反射的核心類型:System.Type類 1、許多支持反射的類型都位于System.Reflection命名空間中,他們是.net Reflection API的一部分,所以再使用的反射的程序中一般都是要使用System.Reflection的命名空間。 2、System.Type類包裝了類型,因此是整個反射子系統(tǒng)的核心,這個類中包含了很多屬性和方法,使用這些屬性和方法可以再運(yùn)行時得到類型的信息。 3、Type類派生于System.Reflection.MemberInfo抽象類
請注意: 1、MemberType屬性的返回類型為MemberTypes,這是一個枚舉,它定義了用于表示不同成元的信息值,這些包括:MemberTypes.Constructor、MemeberTypes.Method、MemberTypes.Event、MemberTypes.Property。因此可以通過檢查MemberType屬性來確定成元的類型,例如在MenberType屬性的值為MemberTypes.Method時,該成員為方法 2、MemberInfo類還包含兩個與特性相關(guān)的抽象方法: (1)GetCustomAttributes():獲得與主調(diào)對象相關(guān)的自定義特性列表。 (2)IsDefined():確定是否為主調(diào)對象定義了相應(yīng)的特性。 (3)GetCustomeAttributesData():返回有關(guān)自定義特性的信息(特性稍后便會提到) 當(dāng)然除了MemberInfo類定義的方法和屬性外,Type類自己也添加了許多屬性和方法:如下表(只列出一些常用的,太多二零,自己可以轉(zhuǎn)定義Type類看一下)
下面列出Type類型定義的常用只讀屬性
四、使用反射 上面將的這些,都是為了使用反射做鋪墊的。 通過使用Type類定義的方法和屬性,我們能夠在運(yùn)行時獲得類型的各種具體信息。這是一個非常強(qiáng)大的功能,我們一旦得到類型信息,就可以調(diào)用其構(gòu)造函數(shù)、方法、屬性,可見,反射是允許使用編譯時不可用的代碼的。 由于Feflection API非常多,這里不可能完整的介紹他們(這里如果完整的介紹,據(jù)說要一本書,厚書)。但是Reflection API是按照一定邏輯設(shè)計的,因此,只要知道部分接口的使用方法,就可以舉一反三的使用剩余的接口。 這里我列出四種關(guān)鍵的反射技術(shù): 1、獲取方法的信息 2、調(diào)用方法 3、構(gòu)造對象 4、從程序集中加載類型 五、獲取方法的相關(guān)信息 一旦有了Type對象就可以使用GetMethodInfo()方法獲取此類型支持的所有方法列表。該方法返回一個MethodInfo對象數(shù)組,MethodInfo對象表述了主調(diào)類型所支持的方法,它位于System.Reflection命名空間中。MethodInfo類派生于MethodBase抽象類,而MethodBase類繼承了MemberInfo類,因此,我們能夠使用這三各類定義的屬性和方法。例如,使用Name屬性的到方法名,這里有兩個重要的成員: 1、ReturnType屬性:為Type類型的對象,能夠提供方法的返回類型信息。 2、GetParameters()方法:返回參數(shù)列表,參數(shù)信息以數(shù)組的形式保存在PatameterInfo對象中。PatameterInfo類定義了大量表述參數(shù)信息的屬性和方法,這里也累出兩個常用的屬性:Name(包含參數(shù)名稱信息的字符串),ParameterType(參數(shù)類型的信息)。 下面代碼我將使用反射獲得類中的所支持的方法,還有方法的信息:
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 //方法的參數(shù)類型名稱
21 Console.Write(pi[i].ParameterType.Name);
22 //方法的參數(shù)名
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 }
輸出結(jié)果:
注意:這里輸出的除了MyClass類定義的所有方法外,也會顯示object類定義的共有非靜態(tài)方法。這是因?yàn)镃#中的所有類型都繼承于Object類。另外,這些信息是在程序運(yùn)行時動態(tài)獲得的,并不需要知道MyClass類的定義 GetMethods()方法的另一種形式 這種形式可以指定各種標(biāo)記,已篩選想要獲取的方法,他的通用形式為:MethodInfo[] GetMethods(BindingFlags bindingAttr) BindingFlags是一個枚舉,枚舉值有(很多,這里只列出5個常用的吧) (1)DeclareOnly:僅獲取指定類定義的方法,而不獲取所繼承的方法 (2)Instance:獲取實(shí)例方法 (3)NonPublic:獲取非公有方法 (4)Public:獲取共有方法 (5)Static:獲取靜態(tài)方法 GetMethods(BindingFlags bindingAttr)這個方法,參數(shù)可以使用 or 把兩個或更多標(biāo)記連接在一起,實(shí)際上至少要有Instance(或 Static)與Public(或 NonPublic)標(biāo)記,否則將不會獲取任何方法。下我們就寫一個示例來演示一下。 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 //不獲取繼承方法,為實(shí)例方法,·為公用的
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 {
21 //方法的參數(shù)類型名稱
22 Console.Write(pi[i].ParameterType.Name);
23 //方法的參數(shù)名
24 Console.Write($" {pi[i].Name}");
25 if (i+1<pi.Length)
26 {
27 Console.Write(", ");
28 }
29 }
30
31 Console.Write(")");
32 Console.Write("\r\n");
33 Console.WriteLine("--------------------------");
34 }
35 Console.ReadKey();
36 }
37 }
38
39 class MyClass
40 {
41 private int x;
42 private int y;
43
44 public MyClass()
45 {
46 x = 1;
47 y = 1;
48 }
49
50 public int Sum()
51 {
52 return x + y;
53 }
54
55 public bool IsBetween(int i)
56 {
57 if (x < i && i < y)
58 {
59 return true;
60 }
61
62 return false;
63 }
64
65 public void Set(int a, int b)
66 {
67 x = a;
68 y = b;
69 }
70
71 public void Set(double a, double b)
72 {
73 x = (int)a;
74 y = (int)b;
75 }
76
77 public void Show()
78 {
79 System.Console.WriteLine($"x:{x},y:{y}");
80 }
81 }
輸出結(jié)果:
上面例子可以看出,只顯示了MyClass類顯示定義的方法,private int Sum() 也不顯示了 六、使用反射調(diào)用方法 上面我們通過反射獲取到了類中的所有信息,下面我們就再使用反射調(diào)用反射獲取到的方法。要調(diào)用反射獲取到的方法,則需要在MethodInfo實(shí)例上調(diào)用Invoke()方法,Invoke()的使用,在下面例子中演示說明: 下面例子是先通過反射獲取到要調(diào)用的方法,然后使用Invoke()方法,調(diào)用獲取到的指定方法:
1 class Program
2 {
3 static void Main()
4 {
5 //獲取描述MyClass類型的Type對象
6 Type t = typeof(MyClass);
7 MyClass reflectObj = new MyClass();
8 reflectObj.Show();
9 //不獲取繼承方法,為實(shí)例方法,·為公用的
10 MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
11 foreach (var methodInfo in mi)
12 {
13
14 //獲取方法闡述列表并保存在ParameterInfo對象組中
15 ParameterInfo[] pi = methodInfo.GetParameters();
16 if (methodInfo.Name.Equals("Set", StringComparison.Ordinal) && pi[0].ParameterType == typeof(int))
17 {
18 object[] args = new object[2];
19 args[0] = 9;
20 args[1] = 10;
21 methodInfo.Invoke(reflectObj,args);
22 }
23 }
24 Console.ReadKey();
25 }
26 }
27
28 class MyClass
29 {
30 private int x;
31 private int y;
32
33 public MyClass()
34 {
35 x = 1;
36 y = 1;
37 }
38
39 public int Sum()
40 {
41 return x + y;
42 }
43
44 public bool IsBetween(int i)
45 {
46 if (x < i && i < y)
47 {
48 return true;
49 }
50
51 return false;
52 }
53
54 public void Set(int a, int b)
55 {
56 x = a;
57 y = b;
58 Show();
59 }
60
61 private void Set(double a, double b)
62 {
63 x = (int)a;
64 y = (int)b;
65 }
66
67 public void Show()
68 {
69 System.Console.WriteLine($"x:{x},y:{y}");
70 }
71 }
獲取Type對象的構(gòu)造函數(shù) 這個之前的闡述中,由于MyClass類型的對象都是顯示創(chuàng)建的,因此使用反射技術(shù)調(diào)用MyClass類中的方法是沒有任何優(yōu)勢的,還不如以普通方式調(diào)用方便簡單呢,但是,如果對象是在運(yùn)行時動態(tài)創(chuàng)建的,反射功能的優(yōu)勢就會顯現(xiàn)出來。在這種情況下,要先獲取一個構(gòu)造函數(shù)列表,然后調(diào)用列表中的某個構(gòu)造函數(shù),創(chuàng)建一個該類型的實(shí)例,通過這種機(jī)制,可以在運(yùn)行時實(shí)例化任意類型的對象,而不必在聲明語句中指定類型。 示例代碼如下:
1 class Program
2 {
3 static void Main()
4 {
5 //獲取描述MyClass類型的Type對象
6 Type t = typeof(MyClass);
7 int val;
8 //使用這個方法獲取構(gòu)造函數(shù)列表
9 ConstructorInfo[] ci = t.GetConstructors();
10 int x;
11 for (x = 0; x < ci.Length; x++)
12 {
13 //獲取當(dāng)構(gòu)造參數(shù)列表
14 ParameterInfo[] pi = ci[x].GetParameters();
15 if (pi.Length == 2)
16 {
17 //如果當(dāng)前構(gòu)造函數(shù)有2個參數(shù),則跳出循環(huán)
18 break;
19 }
20 }
21
22 if (x == ci.Length)
23 {
24 return;
25 }
26 object[] consArgs = new object[2];
27 consArgs[0] = 10;
28 consArgs[1] = 20;
29 //實(shí)例化一個這個構(gòu)造函數(shù)有連個參數(shù)的類型對象,如果參數(shù)為空,則為null
30
31 object reflectOb = ci[x].Invoke(consArgs);
32
33 MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
34 foreach (var methodInfo in mi)
35 {
36 if (methodInfo.Name.Equals("Sum", StringComparison.Ordinal))
37 {
38 val = (int)methodInfo.Invoke(reflectOb, null);
39 Console.WriteLine($"Sum is {val}");
40 }
41 }
42 Console.ReadKey();
43 }
44 }
45
46 class MyClass
47 {
48 private int x;
49 private int y;
50
51 public MyClass(int i)
52 {
53 x = y + i;
54 }
55
56 public MyClass(int i, int j)
57 {
58 x = i;
59 y = j;
60 }
61
62 public int Sum()
63 {
64 return x + y;
65 }
66 }
輸出結(jié)果:
七、從程序集獲得類型 在這之前的闡述中可以看出一個類型的所有信息都能夠通過反射得到,但是MyClass類型本身,我們卻沒有做到獲取,雖然前面的闡述實(shí)例,可以動態(tài)確定MyClass類的信息,但是他們都是基于以下事實(shí):預(yù)先知道類型名稱,并且在typeof與劇中使用它獲得Type對象。盡管這種方式可能在很多情況下都管用,但是要發(fā)揮反射的全部功能,我們還需要分析反射程序集的內(nèi)容來動態(tài)確定程序的可用類型。 借助Reflection API,可以加載程序集,獲取它的相關(guān)信息并創(chuàng)建其公共可用類型的實(shí)例,通過這種機(jī)制,程序能夠搜索其環(huán)境,利用潛在的功能,而無需再編譯期間顯示的定義他們,這是一個非常有效且令人興奮的概念。為了說明如何獲取程序集中的類型,我創(chuàng)建了兩個文件,第一個文件定義一組類,第二個文件則反射各個類型的信息。代碼效果如下: 1、這下面代碼編譯生成MyTest2_C.exe文件 1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Console.WriteLine("Hello word !");
6 Console.ReadKey();
7 }
8 }
9
10 class MyClass
11 {
12 private int x;
13 private int y;
14
15 public MyClass(int i)
16 {
17 x = y + i;
18 }
19
20 public MyClass(int i, int j)
21 {
22 x = i;
23 y = j;
24 }
25
26 public int Sum()
27 {
28 return x + y;
29 }
30 }
2、這下面的代碼時獲取上面生成程序集的 1 class Program
2 {
3 static void Main()
4 {
5 //加載指定的程序集
6 Assembly asm = Assembly.LoadFrom(@"E:\自己的\MyTest\MyTest2_C\bin\Debug\MyTest2_C.exe");
7 //獲取程序集中的所有類型列表
8 Type[] allType = asm.GetTypes();
9 foreach (var type in allType)
10 {
11 //打印出類型名稱
12 Console.WriteLine(type.Name);
13 }
14
15 Console.ReadKey();
16 }
17 }
輸出結(jié)果:
上面獲取到了程序集中的類型,如果像操作程序集類型中的方法,則跟前面我們表述的方法一樣操作即可。 好了,.Net反射我們就介紹到這里啦~ |
|