Attribute(特性)是 C# 中將元數據附加到程序實體(類、方法、屬性、參數等)的強大機制,廣泛用於序列化、驗證、框架擴展、AOP、編譯器指導等場景。本文系統講解預定義特性使用、自定義特性開發、AttributeUsage 控制、繼承規則、運行時反射檢索全流程。
| 特性 | 用途 | 示例 | |
|---|---|---|---|
Obsolete | 標記過時成員,編譯警告/錯誤 | [Obsolete("Use NewMethod()", true)] | |
Conditional | 條件編譯調用 | [Conditional("DEBUG")] | |
Serializable | 標記類可序列化 | [Serializable] | |
AttributeUsage | 控制自定義特性適用目標 | 見下文 | |
CallerMemberName/LineNumber/FilePath | 自動獲取調用者信息 | 日誌、INPC 輔助 | |
DoesNotReturn | 標記方法不返回(永久拋異常) | C# 11+ |
| 屬性 | 類型 | 默認值 | 作用 |
|---|---|---|---|
ValidOn | AttributeTargets (enum, Flags) | AttributeTargets.All | 指定特性可應用的程序實體類型 |
AllowMultiple | bool | false | 同一實體能否多次應用該特性 |
Inherited | bool | true | 派生類/重寫成員是否繼承該特性 |
[Flags]
public enum AttributeTargets {
Assembly = 0x0001, // 程序集級:[assembly: Help("...")]
Module = 0x0002, // 模塊級
Class = 0x0004, // 類、結構體、枚舉、委托
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
ReturnValue = 0x2000, // 返回值
GenericParameter = 0x4000, // 泛型參數
All = Assembly | Module | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate | ReturnValue | GenericParameter,
ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface
}
// 基類
[Help("Base")]
public class Base { }
// 派生類
[Help("Derived")]
public class Derived : Base { }
// 運行時檢索 Derived 類的 HelpAttribute:
// 1. AllowMultiple=false, Inherited=false → 只獲取 Derived (若有) 或無
// 2. AllowMultiple=false, Inherited=true → 只獲取 Derived (覆蓋 Base)
// 3. AllowMultiple=true, Inherited=true → 同時獲取 Base 和 Derived 兩個實例
// 4. AllowMultiple=true, Inherited=false → 只獲取 Derived (若有)
MemberInfo.GetCustomAttributes(typeof(T), inherit):返回 object[]MemberInfo.GetCustomAttributesData():返回 CustomAttributeData[](不實例化特性,僅讀構造參數,性能更好)Attribute.GetCustomAttribute(member, typeof(T)):單個獲取Attribute.IsDefined(member, typeof(T)):判斷是否存在public static void PrintHelp(Type type) {
// 1. 類級特性
var attrs = type.GetCustomAttributes(inherit: true);
foreach (var attr in attrs) {
Console.WriteLine($"[Class] {attr.Description} | Url: {attr.Url}");
}
// 2. 方法級特性
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
var methodAttrs = method.GetCustomAttributes(inherit: false);
foreach (var attr in methodAttrs) {
Console.WriteLine($" [Method:{method.Name}] {attr.Description}");
}
}
}
// 調用
PrintHelp(typeof(UserService));
/*
輸出:
[Class] 核心業務服務 | Url: https://wiki.example.com/UserService
[Method:GetByIdAsync] 根據 ID 異步獲取用戶
*/
public class RequiredAttribute : Attribute { }
public class StringLengthAttribute : Attribute {
public int MaximumLength { get; set; }
public string ErrorMessage { get; set; }
}
public class Validator {
public static void Validate(object obj) {
foreach (var prop in obj.GetType().GetProperties()) {
var value = prop.GetValue(obj);
if (prop.IsDefined(typeof(RequiredAttribute)) && (value == null || (value is string s && string.IsNullOrEmpty(s)))) {
throw new ValidationException($"{prop.Name} 不能為空");
}
var lenAttr = prop.GetCustomAttribute();
if (lenAttr != null && value is string str && str.Length > lenAttr.MaximumLength) {
throw new ValidationException(lenAttr.ErrorMessage ?? $"{prop.Name} 長度超過 {lenAttr.MaximumLength}");
}
}
}
}
// 使用
public class LoginDto {
[Required]
[StringLength(20, ErrorMessage = "用戶名最長 20 字符")]
public string Username { get; set; }
[Required]
[StringLength(100)]
public string Password { get; set; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class RouteAttribute : Attribute {
public string Template { get; }
public RouteAttribute(string template) => Template = template;
}
[AttributeUsage(AttributeTargets.Method)]
public class HttpGetAttribute : Attribute { }
public class HttpPostAttribute : Attribute { }
// 掃描註冊路由
public static void RegisterRoutes(Assembly assembly) {
foreach (var type in assembly.GetTypes()) {
var controllerRoute = type.GetCustomAttribute();
var basePath = controllerRoute?.Template ?? "";
foreach (var method in type.GetMethods()) {
var actionRoute = method.GetCustomAttribute();
var httpMethod = method.GetCustomAttribute() != null ? "GET" :
method.GetCustomAttribute() != null ? "POST" : null;
if (httpMethod != null && actionRoute != null) {
var fullPath = $"{basePath}/{actionRoute.Template}".Replace("//", "/");
Console.WriteLine($"映射路由: {httpMethod} {fullPath} -> {type.Name}.{method.Name}");
}
}
}
}
HelpAttribute、RouteAttribute。使用時可省略後綴:[Help]、[Route]。public HelpAttribute(string desc) → [Help("desc")]。public string Url { get; set; } → [Help("desc", Url="...")]。sealed,防止被繼承導致反射檢索混亂。AssemblyInfo.cs 或任意文件頂部:[assembly: AssemblyVersion("1.0.0")]。掌握 Attribute 的定義、控制與反射檢索,能讓你構建出極具擴展性的框架基礎設施——從模型驗證、路由映射、依賴注入標記、到代碼生成器指導,均以聲明式方式優雅落地。