工作中的 C# 實作:Reflection 反射
1. 什麼是 Reflection (反射)?
Reflection (反射) 是一種強大的 C# 技術,它允許程式在執行時 (runtime) 動態地檢查和操作自己的中繼資料 (metadata)。
你可以把 Reflection 想像成一面特殊的鏡子,你的程式可以透過這面鏡子「看見」自己內部的結構,例如:
- 有哪些類別 (Classes)。
- 每個類別有哪些方法 (Methods)、屬性 (Properties) 或欄位 (Fields)。
- 這些成員的存取修飾詞 (例如
public
、private
)。 - 以及它們的型別和參數。
更厲害的是,Reflection 不只可以「看」,還能動態地「呼叫」這些成員,甚至是在程式碼編譯時完全不知道它們存在的情況下。
2. 為什麼需要 Reflection?
在一般的程式設計中,我們都是在編譯時就確定要使用哪個類別和方法。例如,我們必須先知道 Person
類別有 Name
屬性,才能寫 person.Name
。
但有些情境下,你可能無法在編譯時就確定這些資訊,這時 Reflection 就派上用場了。常見的應用場景包括:
- 動態載入組件 (Assembly):在執行時載入程式碼中未直接引用的 DLL 檔案。
- 序列化與反序列化:將物件轉換成 JSON 或 XML 字串,或從字串中重建物件。因為要處理的物件型別在設計階段可能不確定,需要動態地遍歷所有屬性來讀取或寫入資料。
- 讀取註釋 (Attribute):像是單元測試框架會用 Reflection 尋找所有被
[Test]
註釋標記的方法,然後執行它們。
3. Reflection 語法
使用 Reflection 的核心是 System.Type
類別,它包含了任何型別的所有中繼資料。你可以透過以下兩種方式取得 Type
物件:
方式一:使用 typeof()
運算子
當你在編譯時就知道型別名稱時,這是最簡單的方式。
1 | // 假設我們有一個 Person 類別 |
方式二:使用 GetType()
方法
當你只有一個物件實例,想在執行時知道它的型別時,可以使用這個方法。
1 | Person person = new Person(); |
4. Type.GetProperties()
方法
這是一個非常常用的 Reflection 方法。它用於取得一個型別的所有屬性 (Property) 資訊。它的完整簽名檔通常會帶一個 BindingFlags
參數,用來精確控制你要搜尋的範圍。
例如:
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
這裡面的 BindingFlags
是一個位元遮罩 (Bitmask),可以透過 |
(位元 OR 運算子) 來組合多個標誌,以過濾出你想要的成員。
常見的 BindingFlags
標誌:
BindingFlags.Public
:只取得公開 (public) 的成員。BindingFlags.NonPublic
:只取得非公開 (private, protected, internal) 的成員。BindingFlags.Instance
:只取得屬於物件實例的成員。BindingFlags.Static
:只取得靜態 (static) 的成員。
在上面的程式碼中,BindingFlags.Public | BindingFlags.Instance
的意思是:「請給我所有公開的且屬於物件實例的屬性。」
如果我們忽略這個參數,GetProperties()
會預設取得所有公開的靜態和實例屬性。但為了確保精準性,強烈建議總是使用 BindingFlags
。
5. 範例:動態讀取物件屬性
示範如何使用 Reflection 動態地讀取一個物件的所有公開屬性及其值。
1 | using System; |
程式碼解說:
- 我們定義了一個
User
類別,裡面有公開、私有和靜態屬性。 - 在
Main
方法中,我們用user.GetType()
取得了Type
物件。 - 接著,
GetProperties(BindingFlags.Public | BindingFlags.Instance)
告訴程式我們只對Id
、Name
和Email
這些公開的實例屬性感興趣。 - 最後,我們遍歷
properties
陣列,並使用property.GetValue(user)
這行程式碼,在執行時從user
物件中取出每個屬性的值。
執行結果:
1 | 物件 User 的公開實例屬性: |
從這個結果可以看到,LastLoginTime
和 SystemVersion
都沒有被列印出來,因為它們不符合我們在 BindingFlags
中指定的篩選條件。這完美地展示了 Reflection 的精準和動態能力。