使用C#的反射(Reflection)來達到對物件的進階操作,將任何的物件回歸到最基本的類型。並在基本的類型上進行物件的結構操作
關於反射(Reflection)的說明及利弊可以請教Google大神,這邊就不再多做贅述了
說明
假設目前收到了一個任意物件,並想要知道這個物件的其他訊息,例如型態、是否為函數或是否為類別(Class)等等的基本資訊
判斷物件類型
先來一段程式碼
int val = 10;
var type = val.GetType();
Console.WriteLine($"IsArray: {type.IsArray}");
Console.WriteLine($"IsClass: {type.IsClass}");
Console.WriteLine($"IsEnum: {type.IsEnum}");
Console.WriteLine($"IsInterface: {type.IsInterface}");
Console.WriteLine($"IsPublic: {type.IsPublic}");
Console.WriteLine($"IsGenericType: {type.IsGenericType}");
Console.WriteLine($"IsValueType: {type.IsValueType}");
// 結果
// IsArray: False
// IsClass: False
// IsEnum: False
// IsInterface: False
// IsPublic: True
// IsGenericType: False
// IsValueType: True
從 val.GetType()
取出來的會是 System.Type
類型,上面的範例可以知道裡面有提供很多的屬性來提供判斷即使用,以下是自己比較有用到的供大家參考
屬性 | 說明 |
Name | 變數的名稱 |
Namespace | 該類型所在的命 |
IsArray | 是否為陣列 |
IsClass | 是否為類別 |
IsEnum | 是否為列舉 |
IsGenericType | 是否為泛型 |
IsValueType | 是否為數值 |
下面整理一些常用到的類型供日後快速服用
類型\屬性 | IsArray | IsClass | IsEnum | IsGenericType | IsValueType |
int | V | ||||
int? | V | V | |||
long | V | ||||
string | V | V | |||
string[] | V | V | |||
enum | V | V | |||
List<T> | V | V | |||
Dictionary<T1,T2> | V | V | |||
Class | V |
到這邊要注意一下,假設有一組物件及介面如下
interface IMyInterface {
}
class MyClass : IMyInterface {
}
對物件的操作
這邊的物件主要以類別(Class)為主,因為使用反射(Reflection)的方式來撰寫的話在邏輯上效能是會比較差一點點的,所以在自己的經驗中比較少將反射(Reflection)使用在基本類型(如int、long等等)上。所以接下來將以Class物件為範例
先定義一個類別,方便接下去的操作
class MyClass
{
public int Value1 { get; set; }
public int Value2;
internal int Value3 { get; set; }
internal int Value4;
private int Value5 { get; set; }
private int Value6;
public void SayHello()
{
Console.WriteLine("Hello");
}
public void ShowMessage(string message)
{
Console.WriteLine($"Message: {message}");
}
private void SaySomething(string message)
{
Console.WriteLine($"Say something: {message}");
}
}
存取屬性及欄位的值
下面演示了如何存取Public、Internal及Private的屬性及欄位
var o = new MyClass();
var type = typeof(MyClass); //取得MyClass的Type類型
//設定Public的屬性值
var value1Info = type.GetProperty("Value1");
Console.WriteLine($"Value 1 Before Set: {value1Info.GetValue(o)}");
value1Info.SetValue(o, 20); //裡面的第一個參數是表示要對該實體進行操作
Console.WriteLine($"Value 1 After Set: {value1Info.GetValue(o)}");
// 結果
// Value 1 Before Set: 0
// Value 1 After Set: 20
// ------------------------------------------------------
//設定Public的欄位值
var value2Info = type.GetField("Value2"); //注意這邊是用GetField非GetProperty
Console.WriteLine($"Value 2 Before Set: {value2Info.GetValue(o)}");
value2Info.SetValue(o, 30);
Console.WriteLine($"Value 2 After Set: {value2Info.GetValue(o)2}");
// 結果
// Value 2 Before Set: 0
// Value 2 After Set: 30
// ------------------------------------------------------
//設定Internal的屬性值
var value3Info = type.GetProperty("Value3",
BindingFlags.NonPublic | BindingFlags.Instance); //這邊下方會進行解釋
Console.WriteLine($"Value 3 Before Set: {value3Info.GetValue(o)}");
value3Info.SetValue(o, 40);
Console.WriteLine($"Value 3 After Set: {value3Info.GetValue(o)}");
// 結果
// Value 3 Before Set: 0
// Value 3 After Set: 40
// ------------------------------------------------------
//設定Internal的欄位值
var value4Info = type.GetField("Value4",
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"Value 4 Before Set: {value4Info.GetValue(o)}");
value4Info.SetValue(o, 50);
Console.WriteLine($"Value 4 After Set: {value4Info.GetValue(o)}");
// 結果
// Value 4 Before Set: 0
// Value 4 After Set: 50
// ------------------------------------------------------
//設定Private的屬性值
var value5Info = type.GetProperty("Value5",
BindingFlags.NonPublic | BindingFlags.Instance); //這邊下方會進行解釋
Console.WriteLine($"Value 5 Before Set: {value5Info.GetValue(o)}");
value5Info.SetValue(o, 60);
Console.WriteLine($"Value 5 After Set: {value5Info.GetValue(o)}");
// 結果
// Value 5 Before Set: 0
// Value 5 After Set: 60
// ------------------------------------------------------
//設定Private的欄位值
var value6Info = type.GetField("Value6",
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"Value 6 Before Set: {value6Info.GetValue(o)}");
value6Info.SetValue(o, 70);
Console.WriteLine($"Value 6 After Set: {value6Info.GetValue(o)}");
// 結果
// Value 6 Before Set: 0
// Value 6 After Set: 70
// ------------------------------------------------------
Type.GetProperty(string)
或是Type.GetField(string)
則會直接搜尋public static
及public instance
的成員,相當於使用BindingFlag為BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static
在對物件裡的成員進行操作時都會先取得對應的成員物件,如Type.GetProperty
會返回System.Reflection.PropertyInfo
及Type.GetField
會返回System.Reflection.FieldInfo
。而物件裡面的函數或建構式等其他的成員一樣會有對應的訊息物件(Info Object)。這之後會再做補充。
調用方法
調用方法使用Type.GetMethod
,調用後會取得System.Reflection.MethodInfo
的處理資訊物件,就可以使用這物件來進行函數的處理。
//呼叫 SayHello 函數
var method1 = type.GetMethod("SayHello"); //取得空開的函數
method1.Invoke(o, null); //使用invoke來調用函數,第二個參數null表示沒有參數傳入
// 結果
// Hello
//呼叫 ShowMessage 函數
var method2 = type.GetMethod("ShowMessage");
method2.Invoke(o, new object[] {"顯示訊息喔!!"});
// 結果
// Message: 顯示訊息喔!!
//呼叫 SaySomething 函數
var method3 = type.GetMethod("SaySomething",
BindingFlags.Instance | BindingFlags.NonPublic); //記得要加上Flag
method3.Invoke(o, new object[] {"這是私有函數"});
// 結果
// Say something: 這是私有函數
小總結
- 使用反射可以進入物件的結構進行存取,但要特別注意的是這樣的寫法或多或少會影響到執行效能,以及程式往後的維護性。
- 不管是使用
Type.GetField
或是Type.GetMethod
等相關的方法,若只有填入要取得的成員名稱,那麼預設將會搜尋物件內的public static
及public
的成員,相當於設定BindingFlag為BindingFlags.Public | BindingFlags.Instance
。 - 使用反射進行存取物件或是調用函數時必須很小心型態的問題,畢竟反射是提供在程式執行期間(Runtime)對物件做處理的一項技術,很有可能會有執行其錯誤發生。
- 使用
物件.getType()
與typeof(物件的類別)
所回傳的Type是一樣的,例如上面的o.getType()
與typeof(MyClass)
是一樣的。
如有錯誤煩請不吝指教~~
----------------------------------------------
技術
是需要不斷的訓練
建立絕對的信心
就會在直覺出現的那一刻
毫不猶豫的出手