C#反射與特性(五):型別成員操作
目錄
- 1,MemberInfo
- 1.1 練習-獲取型別的成員以及輸出資訊
- 1.2 MemberType 列舉
- 1.3 MemberInfo 獲取成員方法並且呼叫
- 1.4 獲取繼承中方法的資訊(DeclaringType 和 ReflectedType)
- 2,從 IL 看反射
- 2.1 獲取屬性的構造
- 2.2 屬性的方法
- 3,方法操作
- 3.1 各種方式呼叫方法
【微信平臺,此文僅授權《NCC 開源社群》訂閱號釋出】
前面三篇中,介紹了反射的基本內容和資訊物件,反射主要作用於建構函式、屬性、欄位、方法、事件等型別成員物件;第四篇介紹了型別的例項化和事件操作。
本篇介紹型別的成員操作和實踐練習。
由於內容較多,多動手實踐一下。
那麼,如何通過 Type 獲取相應的成員呢?
以上方法具有獲取單個成員或多個成員的版本。
所有的 *Info 例項都會在第一次使用時,由反射 API 快取起來,這種快取有助於優化 API 的效能。
1,MemberInfo
MemberInfo 可以獲取有關成員屬性的資訊,並提供對成員元資料的訪問許可權。
MemberInfo 類是用於獲取有關類的所有成員(建構函式、事件、欄位、方法和屬性)的資訊的類的抽象基類。
由圖片1可以看到,MemberInfo 是所有反射型別的基類,此類為所有成員提供了基本功能。
使用 GetMember()
或 GetMembers()
可以獲取型別的一個或多個成員。
GetMembers()
該方法會返回當前型別(及其基類)的所有公有成員。
GetMember
方法可以通過名稱檢索特定的成員。由於成員(方法、屬性等)可能會被過載,因此該方法會返回一個數組。
例如
MemberInfo[] members = type.GetMember("test");
1.1 練習-獲取型別的成員以及輸出資訊
建立一個型別
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
1.2 MemberType 列舉
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
。
1.3 MemberInfo 獲取成員方法並且呼叫
下面的例子是通過 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" }));
}
}
}
1.4 獲取繼承中方法的資訊(DeclaringType 和 ReflectedType)
MemberInfo 中,有三種獲取型別的屬性:
- MemberType 獲取成員何種函式(例如欄位、屬性、方法等);
- DeclaringType 該屬性返回該成員的定義型別;
- ReflectedType 返回呼叫 GetMembers 的具體型別;
因為一個方法可以繼承,也可以重寫,那麼很多時候判斷和呼叫,就需要了解相關資訊;
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;
2,從 IL 看反射
筆者的 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()
可以獲取到。
2.1 獲取屬性的構造
定義一個型別
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)
。
2.2 屬性的方法
從反射和 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();
3,方法操作
我們要記得,反射,是對元資料的利用;只有例項才能被執行呼叫。
在這裡,說一下 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 | 執行方法獲取到返回結果 |
3.1 各種方式呼叫方法
首先我們定義一個型別
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);
3.1.1 呼叫方法
我們來呼叫方法 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[] {"測試"});
3.1.2 獲取引數列表
前面 1.1 中,示例有關於獲取方法引數的程式碼。這裡不再贅述
3.1.3 獲取過載方法
在 《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[] {"測試", "測試"});
至此,對於型別、建構函式、委託、方法的例項化與操作,已經講了一次。
下面將說一下屬性和欄位如何設定值和獲取