1、概述 數組
一、經過反射能夠提供類型信息,從而使得咱們開發人員在運行時可以利用這些信息構造和使用對象函數
二、反射機制容許程序在執行過程當中動態地添加各類功能測試
2、運行時類型標識ui
一、運行時類型標誌(RTTI),能夠在程序執行期間判斷對象類型。例如使用他可以確切的知道基類引用指向了什麼類型對象。spa
二、運行時類型標識,能預先測試某個強制類型轉換操做,可否成功,從而避免無效的強制類型轉換異常。.net
三、在C#中有三個支持RTTI的關鍵字:is、as、typeof。下面一次介紹他們設計
is運算符:code
經過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 }
結果:blog
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 }
結果:
3、反射的核心類型:System.Type類
一、許多支持反射的類型都位於System.Reflection命名空間中,他們是.net Reflection API的一部分,因此再使用的反射的程序中通常都是要使用System.Reflection的命名空間。
二、System.Type類包裝了類型,所以是整個反射子系統的核心,這個類中包含了不少屬性和方法,使用這些屬性和方法能夠再運行時獲得類型的信息。
三、Type類派生於System.Reflection.MemberInfo抽象類
MemberInfo類中的只讀屬性 |
|
屬性 |
描述 |
Type DeclaringType |
獲取聲明該成員的類或接口的類型 |
MemberTypes MemberType |
獲取成員的類型,這個值用於指示該成員是字段、方法、屬性、事件、或構造函數 |
Int MetadataToken |
獲取與特定元數據相關的值 |
Module Module |
獲取一個表明反射類型所在模塊(可執行文件)的Module對象 |
String Name |
成員的名稱 |
Type ReflectedType |
反射的對象類型 |
請注意:
一、MemberType屬性的返回類型爲MemberTypes,這是一個枚舉,它定義了用於表示不一樣成元的信息值,這些包括:MemberTypes.Constructor、MemeberTypes.Method、MemberTypes.Event、MemberTypes.Property。所以能夠經過檢查MemberType屬性來肯定成元的類型,例如在MenberType屬性的值爲MemberTypes.Method時,該成員爲方法
二、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 |
獲取指定類型的命名空間 |
4、使用反射
上面將的這些,都是爲了使用反射作鋪墊的。
經過使用Type類定義的方法和屬性,咱們可以在運行時得到類型的各類具體信息。這是一個很是強大的功能,咱們一旦獲得類型信息,就能夠調用其構造函數、方法、屬性,可見,反射是容許使用編譯時不可用的代碼的。
因爲Feflection API很是多,這裏不可能完整的介紹他們(這裏若是完整的介紹,聽說要一本書,厚書)。可是Reflection API是按照必定邏輯設計的,所以,只要知道部分接口的使用方法,就能夠觸類旁通的使用剩餘的接口。
這裏我列出四種關鍵的反射技術:
一、獲取方法的信息
二、調用方法
三、構造對象
四、從程序集中加載類型
5、獲取方法的相關信息
一旦有了Type對象就可使用GetMethodInfo()方法獲取此類型支持的全部方法列表。該方法返回一個MethodInfo對象數組,MethodInfo對象表述了主調類型所支持的方法,它位於System.Reflection命名空間中。MethodInfo類派生於MethodBase抽象類,而MethodBase類繼承了MemberInfo類,所以,咱們可以使用這三各種定義的屬性和方法。例如,使用Name屬性的到方法名,這裏有兩個重要的成員:
一、ReturnType屬性:爲Type類型的對象,可以提供方法的返回類型信息。
二、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() 也不顯示了
6、使用反射調用方法
上面咱們經過反射獲取到了類中的全部信息,下面咱們就再使用反射調用反射獲取到的方法。要調用反射獲取到的方法,則須要在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 }
輸出結果:
7、從程序集得到類型
在這以前的闡述中能夠看出一個類型的全部信息都可以經過反射獲得,可是MyClass類型自己,咱們卻沒有作到獲取,雖然前面的闡述實例,能夠動態肯定MyClass類的信息,可是他們都是基於如下事實:預先知道類型名稱,而且在typeof與劇中使用它得到Type對象。儘管這種方式可能在不少狀況下都管用,可是要發揮反射的所有功能,咱們還須要分析反射程序集的內容來動態肯定程序的可用類型。
藉助Reflection API,能夠加載程序集,獲取它的相關信息並建立其公共可用類型的實例,經過這種機制,程序可以搜索其環境,利用潛在的功能,而無需再編譯期間顯示的定義他們,這是一個很是有效且使人興奮的概念。爲了說明如何獲取程序集中的類型,我建立了兩個文件,第一個文件定義一組類,第二個文件則反射各個類型的信息。代碼效果以下:
一、這下面代碼編譯生成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 }
二、這下面的代碼時獲取上面生成程序集的
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反射咱們就介紹到這裏啦~