首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

C# Attribute (特性) 深度掌握:從預定義到自定義、AttributeUsage 控制與反射檢索

     編輯:更多程式設計知識

Attribute(特性)是 C# 中將元數據附加到程序實體(類、方法、屬性、參數等)的強大機制,廣泛用於序列化、驗證、框架擴展、AOP、編譯器指導等場景。本文系統講解預定義特性使用、自定義特性開發、AttributeUsage 控制、繼承規則、運行時反射檢索全流程。

預定義特性速覽

Obsolete 實戰

public class LegacyApi {
    [Obsolete("請使用 GetUserByIdAsync(int id)", true)] // true = 編譯錯誤
    public User GetUser(int id) => null;

    public Task GetUserByIdAsync(int id) => Task.FromResult(new User());
}

// 調用處會編譯報錯:'LegacyApi.GetUser(int)' is obsolete: '請使用 GetUserByIdAsync(int id)'

自定義特性開發:繼承 System.Attribute

using System;

/// 幫助文檔特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class HelpAttribute : Attribute {
    public string Description { get; }
    public string Url { get; set; } // 可選命名參數

    public HelpAttribute(string description) {
        Description = description;
    }
}

// 使用:類、方法均可附加
[Help("核心業務服務", Url = "https://wiki.example.com/UserService")]
public class UserService {
    [Help("根據 ID 異步獲取用戶")]
    public Task GetByIdAsync(int id) => Task.FromResult(new User());
}

AttributeUsage 三大控制屬性詳解

特性用途示例
Obsolete標記過時成員,編譯警告/錯誤[Obsolete("Use NewMethod()", true)]
Conditional條件編譯調用[Conditional("DEBUG")]
Serializable標記類可序列化[Serializable]
AttributeUsage控制自定義特性適用目標見下文
CallerMemberName/LineNumber/FilePath自動獲取調用者信息日誌、INPC 輔助
DoesNotReturn標記方法不返回(永久拋異常)C# 11+
屬性類型默認值作用
ValidOnAttributeTargets (enum, Flags)AttributeTargets.All指定特性可應用的程序實體類型
AllowMultipleboolfalse同一實體能否多次應用該特性
Inheritedbooltrue派生類/重寫成員是否繼承該特性

AttributeTargets 常用值

[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
}

AllowMultiple 與 Inherited 組合行為表


// 基類
[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 (若有)

運行時反射檢索特性

核心 API

  • 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 異步獲取用戶
*/

進階場景:特性驅動的框架擴展

1. 模型驗證特性(類 DataAnnotations)


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; }
}

2. 控制器路由特性(ASP.NET Core 風格)


[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}");
            }
        }
    }
}

命名慣例與最佳實踐

  1. 類名以 Attribute 結尾:HelpAttribute、RouteAttribute。使用時可省略後綴:[Help]、[Route]。
  2. 構造函數參數 = 位置參數:必填、順序固定。public HelpAttribute(string desc) → [Help("desc")]。
  3. 公開屬性/字段 = 命名參數:選填、順序任意。public string Url { get; set; } → [Help("desc", Url="...")]。
  4. 密封類:特性類建議 sealed,防止被繼承導致反射檢索混亂。
  5. 避免在特性中放置重邏輯:特性僅承載元數據,邏輯由消費者(反射代碼)實現。
  6. 程序集級特性:放在 AssemblyInfo.cs 或任意文件頂部:[assembly: AssemblyVersion("1.0.0")]。

掌握 Attribute 的定義、控制與反射檢索,能讓你構建出極具擴展性的框架基礎設施——從模型驗證、路由映射、依賴注入標記、到代碼生成器指導,均以聲明式方式優雅落地。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved