[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