[C#.NET] 使用反射(Reflection)對物件的結構進行操作 (一)

  • 20582
  • 0
  • C#
  • 2018-11-23

使用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 {
}
那麼建立 MyClass 實體 -> 轉為 IMyInterface -> 取得 Type 後的 IsInterface是False、IsClass是True

 

對物件的操作

這邊的物件主要以類別(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
// ------------------------------------------------------
要存取 private、internal及protected 修飾詞的成員,其 BindingFlags 都是 BindingFlags.NonPublic
如果使用Type.GetProperty(string)或是Type.GetField(string)則會直接搜尋public staticpublic instance的成員,相當於使用BindingFlag為BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static

在對物件裡的成員進行操作時都會先取得對應的成員物件,如Type.GetProperty會返回System.Reflection.PropertyInfoType.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 staticpublic的成員,相當於設定BindingFlag為BindingFlags.Public | BindingFlags.Instance
  • 使用反射進行存取物件或是調用函數時必須很小心型態的問題,畢竟反射是提供在程式執行期間(Runtime)對物件做處理的一項技術,很有可能會有執行其錯誤發生。
  • 使用物件.getType()typeof(物件的類別)所回傳的Type是一樣的,例如上面的o.getType()typeof(MyClass)是一樣的。

 

 

如有錯誤煩請不吝指教~~

----------------------------------------------

技術

是需要不斷的訓練

建立絕對的信心

就會在直覺出現的那一刻

毫不猶豫的出手