一、概述 1、通過反射可以提供類型信息,從而使得我們開發人員在運行時能夠利用這些信息構造和使用對象 2、反射機制允許程式在執行過程中動態地添加各種功能 二、運行時類型標識 1、運行時類型標誌(RTTI),可以在程式執行期間判斷對象類型。例如使用他能夠確切的知道基類引用指向了什麼類型對象。 2、運行時 ...
一、概述
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 {
21 //方法的參數類型名稱
22 Console.Write(pi[i].ParameterType.Name);
23 //方法的參數名
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 }
輸出結果:
上面例子可以看出,只顯示了MyClass類顯示定義的方法,private int Sum() 也不顯示了
六、使用反射調用方法
上面我們通過反射獲取到了類中的所有信息,下麵我們就再使用反射調用反射獲取到的方法。要調用反射獲取到的方法,則需要在MethodInfo實例上調用Invoke()方法,Invoke()的使用,在下麵例子中演示說明:
下麵例子是先通過反射獲取到要調用的方法,然後使用Invoke()方法,調用獲取到的指定方法:
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 //不獲取繼承方法,為實例方法,·為公用的
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對象的構造函數
這個之前的闡述中,由於MyClass類型的對象都是顯示創建的,因此使用反射技術調用MyClass類中的方法是沒有任何優勢的,還不如以普通方式調用方便簡單呢,但是,如果對象是在運行時動態創建的,反射功能的優勢就會顯現出來。在這種情況下,要先獲取一個構造函數列表,然後調用列表中的某個構造函數,創建一個該類型的實例,通過這種機制,可以在運行時實例化任意類型的對象,而不必在聲明語句中指定類型。
示例代碼如下:
1 class Program
2 {
3 static void Main()
4 {
5 //獲取描述MyClass類型的Type對象
6 Type t = typeof(MyClass);
7 int val;
8 //使用這個方法獲取構造函數列表
9 ConstructorInfo[] ci = t.GetConstructors();
10 int x;
11 for (x = 0; x < ci.Length; x++)
12 {
13 //獲取當構造參數列表
14 ParameterInfo[] pi = ci[x].GetParameters();
15 if (pi.Length == 2)
16 {
17 //如果當前構造函數有2個參數,則跳出迴圈
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 //實例化一個這個構造函數有連個參數的類型對象,如果參數為空,則為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