目錄node
【微信平臺,此文僅受權《NCC 開源社區》訂閱號發佈】c#
前面三篇中,介紹了反射的基本內容和信息對象,反射主要做用於構造函數、屬性、字段、方法、事件等類型成員對象;第四篇介紹了類型的實例化和事件操做。數組
本篇介紹類型的成員操做和實踐練習。緩存
因爲內容較多,多動手實踐一下。微信
那麼,如何經過 Type 獲取相應的成員呢?ide
以上方法具備獲取單個成員或多個成員的版本。函數
全部的 *Info 實例都會在第一次使用時,由反射 API 緩存起來,這種緩存有助於優化 API 的性能。性能
MemberInfo 能夠獲取有關成員屬性的信息,並提供對成員元數據的訪問權限。測試
MemberInfo 類是用於獲取有關類的全部成員(構造函數、事件、字段、方法和屬性)的信息的類的抽象基類。優化
由圖片1能夠看到,MemberInfo 是全部反射類型的基類,此類爲全部成員提供了基本功能。
使用 GetMember()
或 GetMembers()
能夠獲取類型的一個或多個成員。
GetMembers()
該方法會返回當前類型(及其基類)的全部公有成員。
GetMember
方法能夠經過名稱檢索特定的成員。因爲成員(方法、屬性等)可能會被重載,所以該方法會返回一個數組。
例如
MemberInfo[] members = type.GetMember("test");
建立一個類型
public class MyClass { private static string A { get; set; } public string B; public string C { get; set; } [Required] public int Id { get; set; } [Phone] public string Phone { get; set; } [EmailAddress] public string Email { get; set; } static MyClass() { A = "666"; } public MyClass() { B = "666"; } public MyClass(string message) { C = message; } public string Add(string a, string b) { return a + b; } }
打印
Type type = typeof(MyClass); MemberInfo[] members = type.GetMembers(); foreach (var item in members) { Console.WriteLine(item.Name + " | " + item.MemberType); }
輸出
get_C | Method set_C | Method get_Id | Method set_Id | Method get_Phone | Method set_Phone | Method get_Email | Method set_Email | Method Add | Method GetType | Method ToString | Method Equals | Method GetHashCode | Method .ctor | Constructor .ctor | Constructor C | Property Id | Property Phone | Property Email | Property B | Field
MemberInfo
中有個 MemberType 枚舉的屬性 名爲 MemberType 。
MemberType
枚舉的定義以下
名稱 | 值 | 說明 |
---|---|---|
All | 191 | 指定全部成員類型 |
Constructor | 1 | 指定該成員是構造函數 |
Custom | 64 | 指定該成員是自定義成員類型 |
Event | 2 | 指定該成員是事件 |
Field | 4 | 指定該成員是字段 |
Method | 8 | 指定該成員是方法 |
NestedType | 128 | 指定該成員是嵌套類型 |
Property | 16 | 指定該成員是屬性 |
TypeInfo | 32 | 指定該成員是類型 |
其中 MemverType.All
的定義以下 All = NestedType | TypeInfo | Property | Method | Field | Event | Constructor
。
下面的例子是經過 GetMembers 獲取到 方法成員,而且傳遞參數調用。
這裏只是示例一下,關於方法的實例化和調用,在本文的第三節。
MemberInfo[] members = type.GetMembers(); foreach (var item in members) { // 若是成員屬於方法 if (item.MemberType == MemberTypes.Method) { // 輸出此方法的參數列表:參數類型+參數名稱 foreach (ParameterInfo pi in ((MethodInfo)item).GetParameters()) { Console.WriteLine("Parameter: Type={0}, Name={1}", pi.ParameterType, pi.Name); } // 若是是方法有兩個參數,則調用 if (((MethodInfo)item).GetParameters().Length == 2) { // 調用一個方法以及傳遞參數 MethodInfo method = (MethodInfo)item; Console.WriteLine("調用一個方法,輸出結果:"); Console.WriteLine(method.Invoke(example, new object[] { "1", "2" })); } } }
MemberInfo 中,有三種獲取類型的屬性:
由於一個方法能夠繼承,也能夠重寫,那麼不少時候判斷和調用,就須要瞭解相關信息;
DeclaringType :一個類型中使用了父類或者本身的方法,那麼返回此方法的出處;
ReflectedType :從哪一個類型中獲取,就返回哪一個類型;即從個 Type 裏得到成員實例,就返回這個 Type 的名稱;
新建一個兩個類型
/// <summary> /// 父類 /// </summary> public class MyClassFather { /// <summary> /// 重寫 ToString() /// </summary> /// <returns></returns> public override string ToString() { return base.ToString(); } } /// <summary> /// 子類 /// </summary> public class MyClassSon : MyClassFather { }
控制檯 Program.Main 中,編寫
Type typeFather = typeof(MyClassFather); Type typeSon = typeof(MyClassSon); Type typeObj = typeof(object); Type typeProgram = typeof(Program); // 爲了省步驟,就不用 MemberInfo 了 MethodInfo methodObj = typeObj.GetMethod("ToString"); MethodInfo methodFather = typeFather.GetMethod("ToString"); MethodInfo methodSon = typeSon.GetMethod("ToString"); MethodInfo methodProgram = typeProgram.GetMethod("ToString");
打印 DeclaringType
Console.WriteLine(methodObj.DeclaringType); Console.WriteLine(methodFather.DeclaringType); Console.WriteLine(methodSon.DeclaringType); Console.WriteLine(methodProgram.DeclaringType);
輸出
System.Object Mytest.MyClassFather Mytest.MyClassFather System.Object
解析:
MyClassFather 對 ToString
方法進行了重寫,因此 DeclaringType
獲取到的類型就是 MyClassFather ;
MyClassSon 繼承了 MyClassFather,直接使用父類的 ToString()
方法,因此返回的是 MyClassFather ;
Program 沒有對 ToString()
進行重寫,因此返回的是 Object;
筆者的 IL 知識很是薄弱,只能列出一些簡單的內容。
在最前面的練習中,咱們發現
public string C { get; set; }
輸出了
get_C | Method set_C | Method C | Property
生成的 IL 是這樣的
.property instance string C() { .get instance string Mytest.MyClass::get_C() .set instance void Mytest.MyClass::set_C(string) }
屬性、索引器、事件生成的 IL 總結:
上面三種類型,生成 IL 時,都會有相應的 方法生成,經過 GetMethods()
或者 GetMembers()
能夠獲取到。
定義一個類型
public class MyClass { private string Test; public string A { get { return Test; } } public string B { set { Test = value; } } public string C { get; set; } }
從前面的實例中,有很多是獲取屬性列表的示例,可是沒法從中識別出裏面的構造,例如上面的 MyClass 類型。
PropertyInfo
中有個 GetAccessors()
方法,能夠獲取相應的信息。
方法 | 使用說明 |
---|---|
GetAccessors() | 返回一個數組,其元素反射了由當前實例反射的屬性的公共 get 和 set 訪問器。 |
GetAccessors(Boolean) | 返回一個數組,其元素反射了當前實例反射的屬性的公共及非公共(若是指定)get 和 set 取值函數。 |
使用示例
Type type = typeof(MyClass); PropertyInfo[] list = type.GetProperties(); foreach (var item in list) { var c = item.GetAccessors(); foreach (var node in c) { Console.WriteLine(node); } Console.WriteLine("************"); }
輸出
System.String get_A() ************ Void set_B(System.String) ************ System.String get_C() Void set_C(System.String) ************
若是將上面的屬性 C 改爲
public string C { get; private set; }
那麼只能輸出
System.String get_A() ************ Void set_B(System.String) ************ System.String get_C() ************
若是想獲取私有構造器,可使用.GetAccessors(true)
。
從反射和 IL 咱們得知,一個屬性會自動生成兩個方法。
那麼咱們經過 PropertyInfo 能夠獲取到這些方法。
方法 | 使用說明 |
---|---|
GetSetMethod | 獲取 set 方法,返回 MethodInfo |
GetGetMethod | 獲取 get 方法,返回 MethodInfo |
GetAccessors | 獲取上面兩個方法的集合,返回 MethodInfo[] |
建立一個屬性
public string C { get; set; }
Type type = typeof(MyClass); PropertyInfo property = type.GetProperty("C"); // 指定獲取 get 或 set MethodInfo set = property.GetSetMethod(); MethodInfo get = property.GetGetMethod(); MethodInfo[] all = property.GetAccessors();
咱們要記得,反射,是對元數據的利用;只有實例才能被執行調用。
在這裏,說一下 nameof
關鍵字,nameof 沒有任何做用,他不會對程序產生任何影響。
nameof(T) 能夠輸出 T,例如 namaof(Program)
輸出 Program
。
那麼什麼狀況下使用到他呢?
咱們在寫代碼時,會使用到例如 Visual Studio 等 IDE,若是使用 nameof,裏面的類型是強類型的,能夠查找引用、跳轉、獲取註釋等。若是須要重構,也能夠快速重命名全部引用。
若是直接使用字符串的話,容易拼錯命名、一旦修改一個命名,須要手動找到全部字符串進行修改。
調用一個實例方法有以下步驟:
步驟 | 類型 | 說明 |
---|---|---|
獲取 Type | Type | 經過程序集等各類方式獲取 Type 類型 |
獲取實例 | object | 經過 Activator.CreateInstance(type); 建立實例 |
獲取方法 | MethodInfo或 MemberInfo | 經過 Type 獲取對應的方法 |
設置參數列表 | object[] parameters | 調用方法時傳遞的參數 |
執行方法 | .Invoke() 方法 | 執行 MethodInfo.Invoke() |
獲取返回結果 | object | 執行方法獲取到返回結果 |
首先咱們定義一個類型
public class MyClass { /// <summary> /// 無參數,五返回值 /// </summary> public void A() { Console.WriteLine("A被執行"); } /// <summary> /// 有參數,有返回值 /// </summary> /// <param name="a"></param> /// <returns></returns> public string B(string left) { Console.WriteLine("執行 B(string left)"); return left + "666"; } public string C(string left,int right) { Console.WriteLine("執行 C(string left,int right)"); return left + right; } public string C(string left, string right) { Console.WriteLine("執行 C(string left, string right)"); return left + right; } }
在 Program 編寫代碼獲取到類型的 Type 以及建立實例。
Type type = typeof(MyClass); object example = Activator.CreateInstance(type);
咱們來調用方法 A()
// 獲取 A MethodInfo methodA = type.GetMethod(nameof(MyClass.A)); // 傳遞實例,而且執行實例的 A 方法 methodA.Invoke(example, new Object[] { });
方法 B
有一個參數,咱們調用時添加參數進去
object result; // 獲取 B MethodInfo methodB = type.GetMethod(nameof(MyClass.B)); // 傳遞參數 // 執行獲取返回結果 result = methodB.Invoke(example, new[] {"測試"});
前面 1.1 中,示例有關於獲取方法參數的代碼。這裏再也不贅述
在 《C# 反射與特性》系列的第四篇,咱們介紹了構造函數 ConstructorInfo 的調用和重載,MethodInfo 實際上也是差很少的。
上面咱們使用了 type.GetMethod("方法名稱")
的方法獲取了 MethodInfo ,對於 MyClass.C
,有兩個重載,那麼咱們能夠這樣指定要使用的重載方法
// 獲取 C // 執行獲取返回結果 MethodInfo methodC = type.GetMethod(nameof(MyClass.C), new Type[] {typeof(string), typeof(string)}); result = methodC.Invoke(example, new string[] {"測試", "測試"}); // result = methodC.Invoke(example, new Object[] {"測試", "測試"});
至此,對於類型、構造函數、委託、方法的實例化與操做,已經講了一次。
下面將說一下屬性和字段如何設置值和獲取值。