1. 介紹
在 C# 裡任何 class(或 struct)都是一組 members。它們包含:
- Fields (欄位): 儲存資料的變數。
- Properties (屬性): 有 get/set 的「智慧」包裝。
- Methods (方法): 功能性程式碼。
- Constructors (建構子): 建立實例的方式。
- Events (事件): 用來發布-訂閱的機制。
想像你有一把魔術放大鏡,可以看透任何型別、知道它的內容,甚至改變它。那就是 reflection。
命名空間與主要的 reflection 型別
開始前別忘了引用命名空間:
using System.Reflection;
重點類別與介面:
| 類別/介面 | 說明 |
|---|---|
|
主要入口 — 一切從它開始 |
|
欄位的資訊 |
|
屬性的資訊 |
|
方法的資訊 |
|
建構子的資訊 |
|
事件的資訊 |
|
以上各者的共通「父類」 |
2. 用 reflection 取得型別的成員
取得 fields 與 properties
範例:列舉欄位與屬性
假設我們有這個 class:
public class Person
{
public string Name { get; set; }
public int Age;
private string Secret = "Sekretnoye slovo";
}
取得欄位:
Type personType = typeof(Person);
FieldInfo[] fields = personType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine($"欄位: {field.Name}, 類型: {field.FieldType.Name}, 存取: {field.Attributes}");
}
取得屬性:
PropertyInfo[] properties = personType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
Console.WriteLine($"屬性: {property.Name}, 類型: {property.PropertyType.Name}");
}
說明
- BindingFlags — 一組「旗標」,用來指定要搜尋哪些成員(public/private、instance/static 等)。
- 欄位和屬性不同:field 是資料片段,property 則是背後的 get/set 方法,封裝了欄位。
取得方法
MethodInfo[] methods = personType.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
Console.WriteLine($"方法: {method.Name}");
}
你會驚訝會列出多少方法。那裡會有屬性的 getter/setter,還有來自 object 的系統方法!
實用過濾:要只取得你自己寫的「顯式」方法,可以依名稱或 attribute 做過濾。
不同情況下該搭配哪些 BindingFlags
| 要找什麼 | 呼叫範例 | 說明 |
|---|---|---|
| 公有欄位 | |
所有對外可見的 |
| 私有欄位 | |
隱藏的內部欄位 |
| 只有實例成員 | |
只有非 static 的 |
| 只有 static | |
只有靜態成員 |
| 全部 | |
完整清單 |
3. 動態建立型別的實例
為什麼這很酷?
你可以在編譯時不知道確切型別的情況下建立物件。
簡單方式 — 用 Activator.CreateInstance
// 以某種方式取得 Type,例如:
Type myType = typeof(Person);
// 建立物件:
object obj = Activator.CreateInstance(myType);
// 現在 obj 是 Person 的實例,但型別是 object
如果 class 沒有無參數的建構子,可以傳參數:
public class Animal
{
public string Name { get; }
public Animal(string name) { Name = name; }
}
// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "Sharik");
重要!
Console.WriteLine(dog.GetType().Name); // Animal
更客製的方式 — 用 ConstructorInfo
ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "Sharik" });
實務上通常用 Activator,但有時需要精確的參數配對或操作 private constructors,這時 ConstructorInfo 會有用。
4. 依名稱存取欄位與屬性
取得並修改欄位
Person p = new Person();
Type t = p.GetType();
FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42
操作 private 欄位:
FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
secret.SetValue(p, "Oj, sekret raskryt");
Console.WriteLine(secret.GetValue(p));
}
else
{
Console.WriteLine("欄位 Secret 未找到");
}
小心!不要濫用存取 private 欄位 — 這會破壞封裝並可能引入 bug。
取得並修改屬性
PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "Vasya");
Console.WriteLine(pi.GetValue(p)); // Vasya
屬性不是欄位,而是背後的 get/set 方法。
為什麼有時候會失敗?
如果你的欄位或屬性是 private,務必加上對應的 BindingFlags(例如 NonPublic)。如果屬性沒有 setter,透過 reflection 就不能寫入 — 這個規則仍然適用。
5. 依名稱呼叫方法
呼叫無參數的方法
public class Greeter
{
public void SayHello()
{
Console.WriteLine("Privet!");
}
}
Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // Privet!
呼叫帶參數的方法
public class MathOp
{
public int Add(int x, int y) => x + y;
}
MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12
如果方法是 static,傳給 Invoke 的第一個參數請傳 null。
處理 overload 的方法
MethodInfo mi = t.GetMethod(
"Add",
new Type[] { typeof(int), typeof(int) }
);
或者你可以列舉並手動過濾:
foreach(var method in t.GetMethods()) {
if (method.Name == "Add" && method.GetParameters().Length == 2)
{
// ... 我們的目標方法!
}
}
6. 好用的小技巧
分析型別時常用的成員、方法與屬性
| 類別成員 | 取得資訊的方法 | 回傳類別 | 如何讀/寫值 |
|---|---|---|---|
| 欄位 (field) | |
|
|
| 屬性 (prop) | |
|
|
| 方法 (method) | |
|
|
| 建構子 | |
|
|
實務上這些功能有什麼用?
- 在 ORM frameworks(例如 Entity Framework)裡會根據你的 models 建 SQL 查詢。
- 在序列化/解析器(例如 JSON serializers)裡會用到。
- 在通用的 logging、testing、UI 生成庫會很常用。
- 面試題常問「你怎麼寫一個通用的物件複製器?」之類的問題。
- 寫 plugin 系統,當程式要載入並呼叫其他人寫的程式碼時很有用。
即使現在覺得這像駭客電影裡的魔術,實際上很實用,會用到的時候你會感謝自己學過。
7. 常見錯誤與注意事項
開發者常在 BindingFlags 上搞混 — 看不到 private 欄位,或是反而抓到太多方法。務必檢查旗標組合:private 欄位用 NonPublic,靜態用 Static 等等。
透過 Invoke 呼叫方法時,參數的順序與類型要和宣告一致。錯誤的順序/類型會導致 TargetParameterCountException 或 ArgumentException。回傳值會以 object 回來 — 別忘了做類型轉換(或用 pattern matching)。
別忘了效能:reflection 比直接呼叫慢。在熱路徑(hot paths)要把找到的 Type/MethodInfo/PropertyInfo 快取起來,或預先產生 delegate。
你會常看到像 get_PropertyName 和 set_PropertyName 的系統方法。那是屬性的存取方法 — 不需要你手動去寫它們。
若你去改變 private 欄位或屬性,就是在破壞封裝。誤用可能造成難以偵測的 bug。正如 Ben 叔叔說的:「有了更大的力量就有更大的責任」— 在 library 類的專案中尤其要小心使用。
GO TO FULL VERSION