☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
C# 基礎 進階

🔮 反射與特性 (Reflection & Attributes)

📌 什麼是反射(Reflection)?

反射就像一面魔鏡,讓你在程式執行時可以「照」出任何物件的內部結構:它有哪些屬性、方法、欄位,甚至可以動態呼叫它們。


📌 取得型別資訊

// 方法 1:透過 typeof 取得型別(編譯時期就知道型別)
Type stringType = typeof(string); // 取得 string 的型別資訊
Console.WriteLine(stringType.FullName); // 印出 "System.String"

// 方法 2:透過物件實例的 GetType() 取得(執行時期才知道)
object myObj = "Hello"; // 宣告一個 object 變數
Type objType = myObj.GetType(); // 取得實際的型別資訊
Console.WriteLine(objType.Name); // 印出 "String"

// 方法 3:透過型別名稱字串取得(完全動態)
Type typeByName = Type.GetType("System.Int32"); // 用字串取得 int 的型別
Console.WriteLine(typeByName.Name); // 印出 "Int32"

📌 檢查屬性與方法

// 定義一個簡單的類別作為示範
public class Student
{
    // 學生姓名
    public string Name { get; set; }

    // 學生年齡
    public int Age { get; set; }

    // 私有欄位:學號
    private string _studentId = "S001";

    // 公開方法:打招呼
    public string SayHello()
    {
        return $"我是 {Name},今年 {Age} 歲"; // 回傳自我介紹
    }

    // 私有方法:取得學號
    private string GetStudentId()
    {
        return _studentId; // 回傳學號
    }
}

// 使用反射檢查 Student 類別
Type studentType = typeof(Student); // 取得 Student 的型別資訊

// 取得所有公開屬性
Console.WriteLine("=== 公開屬性 ==="); // 標題
foreach (var prop in studentType.GetProperties()) // 走訪所有屬性
{
    // 印出屬性名稱和型別
    Console.WriteLine($"  {prop.Name} ({prop.PropertyType.Name})");
}

// 取得所有公開方法
Console.WriteLine("=== 公開方法 ==="); // 標題
foreach (var method in studentType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) // 只取自己宣告的公開實例方法
{
    // 印出方法名稱和回傳型別
    Console.WriteLine($"  {method.Name}() -> {method.ReturnType.Name}");
}

// 取得私有成員(需要特殊的 BindingFlags)
Console.WriteLine("=== 私有欄位 ==="); // 標題
foreach (var field in studentType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) // 取得私有實例欄位
{
    // 印出私有欄位名稱
    Console.WriteLine($"  {field.Name}");
}

📌 動態建立物件與呼叫方法

// 動態建立物件(不用直接 new)
Type type = typeof(Student); // 取得型別
object instance = Activator.CreateInstance(type); // 動態建立 Student 實例

// 動態設定屬性值
PropertyInfo nameProp = type.GetProperty("Name"); // 取得 Name 屬性的資訊
nameProp.SetValue(instance, "小明"); // 把 "小明" 設定給 Name 屬性

PropertyInfo ageProp = type.GetProperty("Age"); // 取得 Age 屬性的資訊
ageProp.SetValue(instance, 20); // 把 20 設定給 Age 屬性

// 動態呼叫方法
MethodInfo sayHello = type.GetMethod("SayHello"); // 取得 SayHello 方法的資訊
object result = sayHello.Invoke(instance, null); // 呼叫方法(null 表示沒有參數)
Console.WriteLine(result); // 印出 "我是 小明,今年 20 歲"

// 動態呼叫私有方法
MethodInfo getId = type.GetMethod("GetStudentId", BindingFlags.NonPublic | BindingFlags.Instance); // 取得私有方法
object id = getId.Invoke(instance, null); // 呼叫私有方法
Console.WriteLine($"學號:{id}"); // 印出 "學號:S001"

📌 特性(Attributes)

特性就像貼在程式碼上的便利貼,用來標記額外的資訊。程式本身不會直接受影響,但其他程式碼(例如框架)可以讀取這些便利貼。

內建特性

// [Obsolete] 標記方法已過時
[Obsolete("請改用 NewMethod(),此方法將在 v3.0 移除")] // 標記為過時
public void OldMethod()
{
    // 舊的實作方式
    Console.WriteLine("這是舊方法");
}

// [Serializable] 標記類別可以被序列化
[Serializable] // 標記此類別的實例可以被序列化
public class Config
{
    // 設定名稱
    public string Name { get; set; }
}

自訂特性

// 建立自訂特性類別
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] // 限制只能用在屬性上,且不能重複
public class ValidateRangeAttribute : Attribute // 繼承 Attribute 基底類別
{
    // 最小值
    public int Min { get; }

    // 最大值
    public int Max { get; }

    // 建構函式,設定範圍
    public ValidateRangeAttribute(int min, int max)
    {
        Min = min; // 儲存最小值
        Max = max; // 儲存最大值
    }

    // 驗證方法
    public bool IsValid(int value)
    {
        return value >= Min && value <= Max; // 檢查值是否在範圍內
    }
}

// 使用自訂特性
public class Product
{
    // 商品名稱
    public string Name { get; set; }

    // 價格必須在 1 到 99999 之間
    [ValidateRange(1, 99999)] // 使用自訂特性標記驗證規則
    public int Price { get; set; }

    // 數量必須在 0 到 1000 之間
    [ValidateRange(0, 1000)] // 使用自訂特性標記驗證規則
    public int Quantity { get; set; }
}

// 用反射讀取自訂特性並執行驗證
public static bool ValidateProduct(Product product)
{
    Type type = product.GetType(); // 取得物件的型別資訊

    foreach (var prop in type.GetProperties()) // 走訪所有屬性
    {
        // 嘗試取得 ValidateRange 特性
        var attr = prop.GetCustomAttribute<ValidateRangeAttribute>();

        if (attr != null) // 如果有標記 ValidateRange
        {
            int value = (int)prop.GetValue(product); // 取得屬性的值
            if (!attr.IsValid(value)) // 驗證是否在範圍內
            {
                // 驗證失敗,印出錯誤訊息
                Console.WriteLine($"{prop.Name} 的值 {value} 不在 {attr.Min}~{attr.Max} 範圍內");
                return false; // 回傳驗證失敗
            }
        }
    }

    return true; // 所有驗證通過
}

📌 何時該用反射?何時不該用?

// ✅ 適合使用反射的情境:
// 1. 框架與函式庫開發(例如 ASP.NET MVC 的 Model Binding)
// 2. 序列化 / 反序列化(JSON、XML)
// 3. 依賴注入容器(DI Container)
// 4. 單元測試中存取私有成員
// 5. 外掛(Plugin)系統,動態載入組件

// ❌ 不適合使用反射的情境:
// 1. 效能敏感的程式碼(反射比直接呼叫慢 10~100 倍)
// 2. 可以用介面(Interface)或泛型解決的問題
// 3. 簡單的物件建立(直接 new 就好)
// 4. 頻繁呼叫的路徑(hot path)

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:BindingFlags 用錯

// ❌ 錯誤寫法:想取得私有方法,但沒加正確的 BindingFlags
Type type = typeof(Student); // 取得型別
MethodInfo method = type.GetMethod("GetStudentId"); // ❌ 回傳 null!預設只搜尋公開方法
method.Invoke(new Student(), null); // 💥 NullReferenceException!
// ✅ 正確寫法:加上 NonPublic 和 Instance 旗標
Type type = typeof(Student); // 取得型別
MethodInfo method = type.GetMethod(
    "GetStudentId",
    BindingFlags.NonPublic | BindingFlags.Instance // ✅ 指定搜尋非公開的實例方法
);
if (method != null) // 先確認方法存在
{
    object result = method.Invoke(new Student(), null); // 安全地呼叫
    Console.WriteLine(result); // 印出結果
}

解釋: GetMethod() 預設只搜尋公開方法。要找私有方法必須明確告訴它搜尋範圍,就像在圖書館找書要去對的樓層。

❌ 錯誤 2:反射效能問題

// ❌ 錯誤寫法:在迴圈中反覆使用反射取得屬性
for (int i = 0; i < 100000; i++) // 十萬次迴圈
{
    Type type = student.GetType(); // 每次都重新取得型別(浪費效能)
    PropertyInfo prop = type.GetProperty("Name"); // 每次都重新搜尋屬性
    string name = (string)prop.GetValue(student); // 每次都透過反射取值
}
// ✅ 正確寫法:快取反射結果,避免重複搜尋
Type type = student.GetType(); // 只取得一次型別
PropertyInfo prop = type.GetProperty("Name"); // 只搜尋一次屬性

for (int i = 0; i < 100000; i++) // 十萬次迴圈
{
    string name = (string)prop.GetValue(student); // 重複使用已快取的 PropertyInfo
}

解釋: 反射的搜尋過程很耗效能。每次在迴圈裡重新搜尋就像每次打電話都重新查電話簿,直接把號碼記下來(快取)會快很多。

❌ 錯誤 3:AttributeUsage 設定錯誤

// ❌ 錯誤寫法:特性標記在錯誤的目標上
[AttributeUsage(AttributeTargets.Method)] // 限制只能用在方法上
public class MyAttribute : Attribute { }

[MyAttribute] // 💥 編譯錯誤!MyAttribute 只能用在方法上,不能用在類別上
public class MyClass { }
// ✅ 正確寫法:根據需求設定正確的 AttributeTargets
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] // ✅ 允許用在類別和方法上
public class MyAttribute : Attribute { }

[MyAttribute] // ✅ 可以用在類別上
public class MyClass
{
    [MyAttribute] // ✅ 也可以用在方法上
    public void MyMethod() { }
}

解釋: AttributeTargets 決定你的便利貼可以貼在哪裡。貼錯地方就像把「小心地滑」的牌子掛在天花板上。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊