一、概述
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反射我們就介紹到這里啦~
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/97988.html
標籤:C#
上一篇:C# detect latest .net framework installed on PC
下一篇:C#面向物件--命名空間
