[C#.NET] 利用特性(Attribute) + 反射(Reflection) 來擴充列舉型別的欄位

[C#.NET] 利用特性(Attribute) + 反射(Reflection) 來擴充列舉型別的欄位

Attribute 在MSDN繁體中文版裡翻成"屬性",Property 也是翻成"屬性",但這兩個用法卻截然不同,以下兩段說明出字MSDN:

屬性 (Property) 就是提供讀取、寫入或計算私用 (Private) 欄位值之彈性機制的成員。 雖然可以將屬性當成公用資料成員使用,不過它們其實是稱為「存取子」(Accessor) 的特殊方法。 這可讓資料更容易存取,並且有助於提升方法的安全性和彈性。

屬性 (Attribute) 提供一個有用的方法,使中繼資料 (或宣告式資訊) 與程式碼 (組件、型別、方法和屬性 (Property) 等) 產生關聯。 當屬性 (Attribute) 與程式實體 (Entity) 產生關聯之後,即可在執行階段使用稱為「反映」(Reflection) 的技術來加以查詢。

相較之下對岸將 Attribute 翻成"特性",個人覺得會"特性"會比"屬性"比較貼切一點,不過今天不是在探討哪個翻譯比較好,先聲明一下,對岸不是所有的翻譯都會令我認同,比如,他們將bit硬翻成"比特",我也是笑了好一段時間;好了,言規正傳,有關Attribute的用法,已經有相當多的前輩已經撰寫過了,最完整的教學我推荐小朱的文章:善用 System.Attribute,讓你的元件更具彈性


接下來,咱們來看一個例子,比如說我的元件裡有一個列舉ReportType型別,元件已經發佈出去,若要為這個列舉型別增加描述或欄位時,思考一下你會怎麼做??


{
    /// 310 全部發送清單 全部發送清單 全部發送清單 
    ///<summary>
    /// 310 全部發送清單 全部發送清單 全部發送清單
    /// </summary>
    All = 310,

    /// 320 成功發送清單
    /// <summary>
    /// 320 成功發送清單
    /// </summary>
    Succeed = 320,

    /// 330 傳送中清單
    /// <summary>
    /// 330 傳送中清單
    /// </summary>
    Sending = 330,

    /// 340 預約簡訊清單
    /// <summary>
    /// 340 預約簡訊清單
    /// </summary>
    PreSend = 340,

    /// 350 逾期簡訊清單
    /// <summary>
    /// 350 逾期簡訊清單
    /// </summary>
    Timeout = 350,

    /// 360 發送失敗清單
    /// <summary>
    /// 360 發送失敗清單
    /// </summary>
    Fail = 360,

    /// 370 回覆簡訊清單
    /// <summary>
    /// 370 回覆簡訊清單
    /// </summary>
    Reply = 370,
}
以下是我想到的辦法?
重寫一個類別取代原本的列舉??這牽動太大,有用到列舉的部份都要修改,犧牲真的很大。
用列舉當參數傳入某一類別或方法來擴充功能??資料的取得過程可能會變得複雜,欄位越多判斷式可能會更多。
用列舉當參數傳入IO檔案或是資料庫來當查詢條件??會比上面的方法更複雜,因為又多了一層IO,等等…。
用Attribute+反射來擴充??反射,效能會有所損耗,但是會帶來其它更多的效益。
上述方式都可行,要選哪個都可以,不過以彈性及擴充性來講,我會選擇 Attribute + Reflection,
反射(Reflection),會減少hard code的時間,相對的也會造成效能上的損失,不過可以提升可靠度(關鍵的code變少了)跟可維護度(彈性增加了),在這裡是我選擇使用它的原因。
特性(Attribute),同樣的也會減少相當多的類別定義,若不使用Attribute,你可能必須這樣做:

{
    public int ReportId { get; set; }

    public string ReportName { get; set; }

    public string Description { get; set; }
}

public class ReportTypeExtends : List<ReportTypeExtend>
{
    public ReportTypeExtends()
    {
        this.Add(new ReportTypeExtend() { ReportId = 310, ReportName = "All", Description = "全部發送清單" });
        this.Add(new ReportTypeExtend() { ReportId = 320, ReportName = "Succeed", Description = "成功發送清單" });        
//…想到還有好多欄位要Key in就要哭了,若下次有欄位要新增,苦工還真不少。 } }
我無法想像在不使用Attribute,又不動到原本設計的情況下,能有其它的好辦法。

接下來咱們來實做:
首先,依規則定義一個Attribute類別,定義規則請參考小朱的文章

class ReportDescriptionAttribute : Attribute
{
    public string Description { get; set; }

    public ReportDescriptionAttribute(string Description)
    {
        this.Description = Description;
    }

    public override string ToString()
    {
        return this.Description.ToString();
    }
}
定義好之後,我們就可以把它掛在列舉的欄位上,比起自己Mapping類別快多了

{
    /// 310 全部發送清單 
/// <summary>
/// 310 全部發送清單 /// </summary> [ReportDescription("全部發送清單")] All = 310, /// 320 成功發送清單 /// <summary> /// 320 成功發送清單 /// </summary> [ReportDescription("成功發送清單")] Succeed = 320, /// 330 傳送中清單 /// <summary> /// 330 傳送中清單 /// </summary> [ReportDescription("傳送中清單")] Sending = 330, /// 340 預約簡訊清單 /// <summary> /// 340 預約簡訊清單 /// </summary> [ReportDescription("預約簡訊清單")] PreSend = 340, /// 350 逾期簡訊清單 /// <summary> /// 350 逾期簡訊清單 /// </summary> [ReportDescription("逾期簡訊清單")] Timeout = 350, /// 360 發送失敗清單 /// <summary> /// 360 發送失敗清單 /// </summary> [ReportDescription("發送失敗清單")] Fail = 360, /// 370 回覆簡訊清單 /// <summary> /// 370 回覆簡訊清單 /// </summary> [ReportDescription("回覆簡訊清單")] Reply = 370, }
短短幾行程式碼,就能取得Attribute的屬性定義。

{
    var members = typeof(ReportType).GetMember(reportType.ToString());
    var attributes = members[0].GetCustomAttributes(typeof(ReportDescriptionAttribute), false);
    var description = ((ReportDescriptionAttribute)attributes[0]).Description;
    return description;
}

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo