一、概述
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 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 }
輸出結果:
七、從程序集獲得類型
在這之前的闡述中可以看出一個類型的所有信息都能夠通過反射得到,但是MyClass類型本身,我們卻沒有做到獲取,雖然前面的闡述實例,可以動態確定MyClass類的信息,但是他們都是基於以下事實:預先知道類型名稱,並且在typeof與劇中使用它獲得Type對象。盡管這種方式可能在很多情況下都管用,但是要發揮反射的全部功能,我們還需要分析反射程序集的內容來動態確定程序的可用類型。
借助Reflection API,可以加載程序集,獲取它的相關信息並創建其公共可用類型的實例,通過這種機制,程序能夠搜索其環境,利用潛在的功能,而無需再編譯期間顯示的定義他們,這是一個非常有效且令人興奮的概念。為了說明如何獲取程序集中的類型,我創建了兩個文件,第一個文件定義一組類,第二個文件則反射各個類型的信息。代碼效果如下:
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 }
輸出結果:
上面獲取到了程序集中的類型,如果像操作程序集類型中的方法,則跟前面我們表述的方法一樣操作即可。
好了,.Net反射我們就介紹到這里啦~