在 C# 中,反射(Reflection)和特性(Attributes)是兩個強大的功能,它們在運行時提供元編程能力,廣泛用于框架開發、對象映射和動態行為擴展。以下是對它們的詳細介紹,包括定義、用法、示例和應用場景。
一、反射(Reflection)
什么是反射?
反射是 C# 運行時的一種機制,允許程序在運行時動態檢查和操作類型、對象及其元數據(如類、方法、屬性等)。通過反射,開發者可以:
- 獲取類型信息(如類名、方法名)。
- 動態創建對象。
- 調用方法或訪問屬性/字段。
- 檢查或修改私有成員(需注意權限)。
反射的核心類庫位于 System.Reflection
命名空間。
反射的核心類和方法
-
Type
:- 表示類型的元數據,是反射的核心。
- 獲取方式:
typeof(ClassName)
:靜態獲取類型。object.GetType()
:從實例獲取類型。
-
Assembly
:- 表示程序集,可以加載和檢查 DLL 或 EXE。
-
MethodInfo
、PropertyInfo
、FieldInfo
:- 分別表示方法、屬性和字段的元數據。
-
Activator
:- 用于動態創建對象實例。
示例 1:基本反射操作
using System;
using System.Reflection;class Person
{public string Name { get; set; }private int age = 25;public void SayHello(){Console.WriteLine($"Hello, I'm {Name}, {age} years old.");}
}class Program
{static void Main(){// 獲取類型Type type = typeof(Person);Console.WriteLine($"類名: {type.Name}");// 創建實例object instance = Activator.CreateInstance(type);// 設置屬性PropertyInfo nameProp = type.GetProperty("Name");nameProp.SetValue(instance, "Alice");// 獲取私有字段并修改FieldInfo ageField = type.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);ageField.SetValue(instance, 30);// 調用方法MethodInfo method = type.GetMethod("SayHello");method.Invoke(instance, null);// 輸出所有公共方法Console.WriteLine("\n公共方法:");foreach (MethodInfo m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)){Console.WriteLine(m.Name);}}
}
輸出
類名: Person
Hello, I'm Alice, 30 years old.公共方法:
get_Name
set_Name
SayHello
ToString
Equals
GetHashCode
GetType
說明
typeof
:獲取Person
的類型信息。Activator.CreateInstance
:動態創建實例。GetProperty
和SetValue
:訪問和修改屬性。GetField
:通過BindingFlags
獲取私有字段。Invoke
:動態調用方法。
示例 2:加載程序集
using System;
using System.Reflection;class Program
{static void Main(){// 加載當前程序集Assembly assembly = Assembly.GetExecutingAssembly();foreach (Type type in assembly.GetTypes()){Console.WriteLine($"類型: {type.FullName}");}}
}
輸出
類型: Program
說明
Assembly.GetExecutingAssembly
:獲取當前程序集。GetTypes
:列出程序集中所有類型。
反射的優缺點
- 優點:
- 動態性:運行時決定行為,適合插件系統或框架。
- 靈活性:無需提前知道類型即可操作。
- 缺點:
- 性能開銷:反射比直接調用慢。
- 安全性:可能暴露私有成員,需謹慎使用。
二、特性(Attributes)
什么是特性?
特性是 C# 中的一種聲明性標簽,用于為代碼元素(如類、方法、屬性等)附加元數據。特性在運行時可以通過反射讀取,用于控制行為或提供額外信息。
特性定義在 System
命名空間中,常用基類是 Attribute
。
特性的定義與使用
-
定義特性:
- 繼承自
Attribute
,添加[AttributeUsage]
指定適用范圍。
- 繼承自
-
應用特性:
- 使用方括號
[ ]
標記在代碼元素上。
- 使用方括號
-
讀取特性:
- 通過反射的
GetCustomAttributes
方法獲取。
- 通過反射的
示例 1:自定義特性
using System;
using System.Reflection;// 定義特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class DescriptionAttribute : Attribute
{public string Description { get; }public DescriptionAttribute(string description){Description = description;}
}// 使用特性
[Description("這是一個測試類")]
class TestClass
{[Description("這是一個測試方法")]public void TestMethod(){Console.WriteLine("Hello from TestMethod!");}
}class Program
{static void Main(){// 獲取類特性Type type = typeof(TestClass);DescriptionAttribute classAttr = (DescriptionAttribute)Attribute.GetCustomAttribute(type, typeof(DescriptionAttribute));Console.WriteLine($"類描述: {classAttr?.Description}");// 獲取方法特性MethodInfo method = type.GetMethod("TestMethod");DescriptionAttribute methodAttr = (DescriptionAttribute)method.GetCustomAttribute(typeof(DescriptionAttribute));Console.WriteLine($"方法描述: {methodAttr?.Description}");// 調用方法object instance = Activator.CreateInstance(type);method.Invoke(instance, null);}
}
輸出
類描述: 這是一個測試類
方法描述: 這是一個測試方法
Hello from TestMethod!
說明
[AttributeUsage]
:限制特性只能用于類和方法,且不可重復。GetCustomAttribute
:獲取指定類型的特性實例。Description
:特性中存儲的元數據。
示例 2:內置特性 - [Obsolete]
using System;class Program
{[Obsolete("此方法已過時,請使用 NewMethod", false)] // false 表示警告,true 表示錯誤static void OldMethod(){Console.WriteLine("Old Method");}static void NewMethod(){Console.WriteLine("New Method");}static void Main(){OldMethod(); // 編譯器會發出警告NewMethod();}
}
輸出(帶警告)
Old Method
New Method
說明
[Obsolete]
:標記方法為過時,編譯時提示開發者。
特性的應用場景
-
框架開發:
- ASP.NET Core 使用
[Route]
、[HttpGet]
等特性定義路由和行為。 - Entity Framework 使用
[Table]
、[Key]
配置數據庫映射。
- ASP.NET Core 使用
-
驗證與描述:
[Required]
、[MaxLength]
用于數據驗證。[Description]
添加文檔信息。
-
條件編譯:
[Conditional("DEBUG")]
在特定條件下執行方法。
反射與特性的結合
反射和特性經常一起使用,例如:
- 依賴注入:通過反射掃描帶有特定特性的類,動態注入。
- 序列化:檢查
[Serializable]
或自定義特性,決定序列化字段。
優缺點
- 優點:
- 聲明式編程:減少硬編碼,提高可維護性。
- 元數據豐富:為工具和框架提供信息。
- 缺點:
- 運行時開銷:讀取特性需要反射。
- 復雜度:過度使用可能使代碼難以理解。
總結
- 反射:運行時動態操作類型和對象,適合需要靈活性的場景(如插件系統)。
- 特性:為代碼添加元數據,配合反射實現聲明式邏輯(如框架配置)。
通過反射,你可以動態調用方法或創建實例;通過特性,你可以為代碼附加規則或描述。這兩者在 C# 中是構建高級功能(如 ORM、AOP)的基石。