一、特性(Attribute)定義
特性(Attribute)是用於在運行時傳遞程序中各種元素(比如類、方法、結構、枚舉、組件等)的行為信息的聲明性標簽。您可以通過使用特性向程序添加聲明性信息。一個聲明性標簽是通過放置在它所應用的元素前面的方括號([ ])來描述的。
特性(Attribute)用於添加元數據,如編譯器指令和注釋、描述、方法、類等其他信息。.Net 框架提供了兩種類型的特性:預定義特性和自定義特性。
二、How (如何使用)
2.1 .Net 框架預定義特性使用:
- AttributeUsage
[AttributeUsage(validon,AllowMultiple=allowmultiple,Inherited=inherited)] 其中:參數 validon 規定特性可被放置的語言元素。它是枚舉器 AttributeTargets 的值的組合。默認值是 AttributeTargets.All,表示該特性可以修飾在任何地方。比如:AttributeTargets.Class 則表示該特性只能修飾在類上面。
- Conditional
這個預定義特性標記了一個條件方法,其執行依賴於指定的預處理標識符。它會引起方法調用的條件編譯,取決於指定的值,比如 Debug 或 Trace。
- Obsolete
這個預定義特性標記了不應被使用的程序實體。它可以讓您通知編譯器丟棄某個特定的目標元素。例如,當一個新方法被用在一個類中,但是您仍然想要保持類中的舊方法,您可以通過顯示一個應該使用新方法,而不是舊方法的消息,來把它標記為 obsolete(過時的)。規定該特性的語法:[Obsolete(message,iserror)]
參數 message,是一個字符串,描述項目為什么過時以及該替代使用什么。
參數 iserror,是一個布爾值。如果該值為 true,編譯器應把該項目的使用當作一個錯誤。默認值是 false(編譯器生成一個警告)。
2.2 自定義特性使用
使用場景:某些時候我們在程序處理過程中為了程序更加健全及安全需要校驗一些參數的合法性,比如郵件格式校驗等類似的需求,以下以郵件合法及公司CompanyId的范圍只能在1000~10000范圍為例。剛開始我們最直接也最先想到的就是傳統的方式寫參數校驗。如下所示:
1 #region 傳統的方式寫參數校驗 2 { 3 4 if (string.IsNullOrWhiteSpace(user.Email)) //:這里只判斷了郵箱為空,省略了郵箱合法性判斷 5 { 6 Console.WriteLine("Email 參數不符合!"); 7 //:這里不執行保存,提示用戶參數不合法 8 } 9 if (user.CompanyId < 1000 || user.CompanyId > 10000) 10 { 11 Console.WriteLine("CompanyId 參數不符合,CompanyId范圍只能是1000~10000"); 12 //:這里不執行保存,提示用戶參數不合法 13 } 14 } 15#endregion
問題分析:假如某一天業務需求發生了變化,新增了一個參數的校驗或者CompanyId的范圍發生改變,我們就需要修改代碼。代碼不穩定
解決方案:特性:可以在不破壞類型封裝的前提下,為對象增加額外的信息,執行額外的行為。通過特性我們把公共邏輯移出去,只完成私有邏輯。如下所示,考慮到程序后期可能會涉及到多種參數的校驗,每種參數要實現的校驗規則各不相同,所以我們定義一個抽象類,抽象類中定義一個抽象方法。
1、定義一個抽象類,繼承自Attribute,如下所示:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace MyAttribute.AttributeExtend 8 { 9 [AttributeUsage(AttributeTargets.Property)] 10 public abstract class AbstractValidateAttribute : Attribute 11 { 12 public abstract bool Validate(object oValue); 13 } 14 }
2、 校驗郵箱的具體實現如下,繼承自AbstractValidateAttribute。EmailValidateAttribute 類實現校驗郵箱的邏輯
1 [AttributeUsage(AttributeTargets.Property)] 2 public class EmailValidateAttribute : AbstractValidateAttribute 3 { 4 public override bool Validate(object oValue) 5 { 6 if (oValue != null && string.IsNullOrWhiteSpace(oValue.ToString())) 7 { 8 return true;//實際的判斷需要正則驗證、此處省略 9 } 10 else 11 { 12 return false; 13 } 14 } 15 }
IntValidateAttribute 繼承自AbstractValidateAttribute。 驗證int類型是否在取值范圍內的邏輯
1 /// <summary> 2 /// 驗證int類型是否在取值范圍內 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Property)] 5 public class IntValidateAttribute : AbstractValidateAttribute 6 { 7 private int _Min = 0; 8 private int _Max = 0; 9 10 public IntValidateAttribute(int min, int max) 11 { 12 this._Min = min; 13 this._Max = max; 14 } 15 16 public override bool Validate(object oValue) 17 { 18 return oValue != null && int.TryParse(oValue.ToString(), out int num) && num >= this._Min && num <= this._Max; 19 } 20 }
3、通過反射調用特性
1 /// <summary> 2 /// 數據庫訪問基類 3 /// </summary> 4 public class BaseDAL 5 { 6 /// <summary> 7 /// 增加校驗 8 /// </summary> 9 /// <typeparam name="T"></typeparam> 10 /// <param name="t"></param> 11 public static void Save<T>(T t) 12 { 13 Type type = t.GetType(); 14 bool isSafe = true; 15 { 16 foreach (var property in type.GetProperties()) 17 { 18 object[] oAttributeArray = property.GetCustomAttributes(typeof(AbstractValidateAttribute), true);//特性類的實例化就在反射發生的時候 19 foreach (var oAttribute in oAttributeArray) 20 { 21 AbstractValidateAttribute validateAttribute = oAttribute as AbstractValidateAttribute; 22 isSafe = validateAttribute.Validate(property.GetValue(t)); 23 if (!isSafe) 24 { 25 break; 26 } 27 } 28 if (!isSafe) 29 { 30 break; 31 } 32 } 33 } 34 35 if (isSafe) 36 { 37 Console.WriteLine("保存到數據庫"); 38 //:執行業務數據保存操作 39 } 40 41 else 42 { 43 Console.WriteLine("數據不合法"); 44 //:不執行保存,提示用戶參數不合法 45 } 46 } 47 }
4、使用特性
1 public class UserModel 2 { 3 4 #region Model 5 /// <summary> 6 /// EMaill 7 /// </summary> 8 [EmailValidate] //:標記特性 9 public string Email 10 { 11 set; 12 get; 13 } 14 15 /// <summary> 16 /// 企業ID 17 /// </summary> 18 [IntValidate(1000, 10000)] //:標記特性 19 public int CompanyId 20 { 21 set; 22 get; 23 } 24 /// <summary> 25 /// 企業名稱 26 /// </summary> 27 public string CompanyName 28 { 29 set; 30 get; 31 } 32 /// <summary> 33 /// 用戶狀態 0正常 1凍結 2刪除 34 /// </summary> 35 public int? State 36 { 37 set; 38 get; 39 } 40 41 #endregion Model 42 43 }
3、演示結果
1、上端調用
2、反射找到所有的屬性,及每一個屬性的自定義AbstractValidateAttribute標簽的特性
3、根據不同的特性執行對應的方法
4、總結:
后期如果新增不同參數類型的校驗,只需要新增對應的類,繼承自AbstractValidateAttribute ,在新增的類中實現具體的校驗邏輯,並且在需要校驗的屬性上標記對應的特性即可,方便代碼擴展。