.Net 特性分析與妙用
一.特性是什麼
1、想象很多小夥伴們都看過在一個類上方、或者在控制器見過類似的東東,加上之後就可以標識這個類或者方法就具備了某些特點 ,那我們就進入它的內心一探究竟吧。
2.我們進入某個特性之後,可以發現它又單獨繼承於Attribute 它的意思就是屬性、特質的意思。那它到底能幹嘛呢?能讓我們寫程式碼飛起嗎??
二.走進特性
1.我們也寫一個自己的特性,說幹就來吧來。帶著問題一步一步是魔鬼的步伐,兄弟們要我們幹就完了,噢力給!!!
2.首先我們建立一個類(特性就是一個很單純的類,我們一般以Attribute結尾命名),像這樣我們的特性類就做好了,接下來怎麼做呢?
3.建立完之後當然是使用它,它可以使用在類、方法、屬性、列舉、引數、返回值上,使用的時候可以預設的省略Attribute,當我們使用[User]的時候就預設使用了[User()]就是在呼叫特性類的預設建構函式,特性必須存在一個或者多個構造方法,我們使用的時候就相當於執行特性的建構函式。好了知道怎麼使用特性了,但是特性有什麼卵用我還是不懂的樣子!!!
4.上面我們已經知道了特性的建立和新增,但是還沒有體現出有什麼好處,在剛才我們建立了一個特性但是我們沒有寫任何程式碼,下面我們在建立一個新的特性進行使用。
我們假裝一個場景我們需要判斷使用者支付訂單的支付狀態,我們會寫一個列舉1待支付、2支付成功、3支付失敗。
上面應該會是我們經常寫的程式碼了吧,雖然說現在只有幾個型別,但是型別多起來之後這裡的判斷就顯得很冗餘,又長又臭了。這裡就不拐彎抹角我們可以使用特性去實現簡單化。
/// <summary> /// 獲取型別名稱的特性 /// </summary> public class RemarkAttribute : Attribute { /// <summary> /// 接受備註欄位 /// </summary> public string remarks { get; set; } /// <summary> /// 建構函式 /// </summary> public RemarkAttribute(string _remarks) { remarks = _remarks; } /// <summary> /// 返回備註欄位 /// </summary> /// <returns></returns> public string GetRemarks() { return this.remarks; } }View Code
上面我們建立了一個返回備註欄位的特性,注意特性就是一個類,當然就擁有類所有東西,我們定義一個有參建構函式就收傳過來的備註存在remarks屬性中,然後通過GetRemarks()方法返回備註資訊,然後我們怎麼使用呢?我們在剛才的列舉中加入我們的特性,上面我們說過特性可以新增在列舉上
/// <summary> /// 定義列舉 /// </summary> public enum TypeEnum { /// <summary> /// 待支付 /// </summary> [Remark("待支付")] ToPay = 1, /// <summary> /// 支付成功 /// </summary> [Remark("支付成功")] PaymentSuccess = 2, /// <summary> /// 支付失敗 /// </summary> [Remark("支付失敗")] PaymentFailure = 3 }View Code
好了我們現在準備工作都做好了,特性我們也寫了,使用特性的地方我們也加上了,但是我們要怎麼獲取特性的資訊呢?一臉懵逼了,別急兄弟們慢慢來。這裡就要使用反射,反射的特點你們還記得嗎?它可以獲取某個類,獲取類的特性、屬性、欄位等等,還可以獲取屬性的特性。(反射篇)
/// <summary> /// 編寫一個列舉的拓展類 /// </summary> public static class EnumExpand { /// <summary> /// 獲取列舉欄位的返回備註 /// </summary> /// <param name="enum"></param> /// <returns></returns> public static string GetTypeName(this Enum value) { //首先我們獲取type型別 Type type = value.GetType(); //這裡直接存起來方便返回 string strValue = value.ToString(); //然後我們是獲取欄位上面的特性,所以這裡是一個 FieldInfo field = type.GetField(strValue); ///IsDefined判斷型別是不是有這個型別。 ///第二個屬性:如果是真會根據繼承鏈找是不是有這個型別 if (field.IsDefined(typeof(RemarkAttribute), true)) { //GetCustomAttribute獲取型別的特性.(這個的時候會去獲取之前[User("111")]的類,相當於new,這個就是建構函式) //第一個引數是型別 //第二個:如果是真會看自己的祖先有沒有這個型別 RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true); return attribute.GetRemarks(); } else { return strValue; } } }View Code
上面是我們寫的一個列舉的拓展方法,方便我們的使用。因為我們是在列舉的欄位使用特性,這裡獲取呼叫方法的類,進行反射獲取裡面的成員(這裡我們是在欄位上面使用特性,所以獲取列舉在獲取裡面的欄位。)這樣是不是比我們以前這麼多判斷更加方便簡潔呢?
class Program { static void Main(string[] args) { TypeEnum typeEnum = TypeEnum.PaymentFailure; Console.WriteLine(typeEnum.GetTypeName()); } }View Code
5.特性的限制
/// <summary> /// AttributeUsage是特性的約束。 /// 第一個是限制這個特性可以作用在什麼上面, /// 第二個是顯示這個特性可以不可以重複新增到一個上面 /// 第三個引數是限制特性可以不可以被繼承到,把特性遺傳到繼承者上面 /// </summary>
[AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]
特性也可以限制使用範圍,AttributeTargets 限制使用的物件上面, AllowMultiple是否可以在同一個物件上面使用多次,預設是false,Inherited表示該特性是否可以被繼承,預設情況下Inherited為true。
三、特性進階
1.看到這裡我相信我們的小夥伴已經知道了什麼是特性以及編寫屬於自己的特性啦。為了鞏固大家的記憶我們在來一個大家經常遇到的場景
情景劇場開始:我們經常需要判斷使用者輸入的資料是不是正確的,經常需要些大量的判斷,程式碼又長又重複(說到這裡使用過EF的小夥伴肯定知道,可以在模型裡面做限制哈哈)我們做兩個場景需要判斷某個欄位的長度,以及大小.
/// <summary> /// 繼承抽象類,顯示抽象類裡面的方法 /// </summary> public class ScopeAttribute : VerifyBaseAtttribute { /// <summary> /// 最小值 /// </summary> private int Min { get; set; } = 0; /// <summary> /// 最大值 /// </summary> private int Max { get; set; } = 0; /// <summary> /// 限制長度 /// </summary> /// <param name="min"></param> /// <param name="max"></param> public ScopeAttribute( int max = 0) { this.Max = max; } /// <summary> /// 限制長度 /// </summary> /// <param name="min"></param> /// <param name="max"></param> public ScopeAttribute(int min = 0, int max = 0) { this.Min = min; this.Max = max; } /// <summary> /// 判斷長度範圍修飾 /// </summary> /// <returns></returns> public override bool Verify(object value) { if (value == null) { return false; } Type type = value.GetType(); string str = value.ToString(); if (string.IsNullOrWhiteSpace(str)) { return false; } int Length = str.Length; if (Min > 0 && Max > 0) { return Length > Min && Length < Max ? true : false; } else if (Min > 0) { return Length > Min ? true : false; } else if (Max > 0) { return Length < Max ? true : false; } return false; } }View Code
/// <summary> /// 判斷qq號 /// </summary> public class QQAttribute : VerifyBaseAtttribute { /// <summary> /// 最小值 /// </summary> private int Min { get; set; } /// <summary> /// 最大值 /// </summary> private int Max { get; set; } /// <summary> /// 判斷區間數 /// </summary> /// <param name="min"></param> /// <param name="max"></param> public QQAttribute(int min = 0, int max = 0) { this.Min = min; this.Max = max; } /// <summary> /// 判斷長度範圍修飾 /// </summary> /// <returns></returns> public override bool Verify(object value) { if (value == null) { return false; } Type type = value.GetType(); if (type == typeof(int)) { int valueInt = Convert.ToInt32(value); if (valueInt == 0) return false; return valueInt > Min && valueInt < Max ? true : false; } return false; } }View Code
/// <summary> /// 驗證父類特性 /// 主要就是給驗證特性提供一個統一的入口 /// 實現方式:介面形式、抽象類形式進行重寫(這裡我使用抽象類實現) /// </summary> public abstract class VerifyBaseAtttribute : Attribute { /// <summary> /// 統一驗證的入口 /// </summary> /// <returns></returns> public abstract bool Verify(object value); }View Code
看了上面三段程式碼你就會很疑惑為什麼,你寫了一個抽象的特性?????滿臉問號等下就告訴你。
public static class EntityExpands { /// <summary> /// 驗證方法 /// </summary> /// <typeparam name="T">泛型</typeparam> /// <param name="model">實體</param> /// <returns></returns> public static bool Verify<T>(this T model) where T : class, new()//限制這個拓展方法只能使用在可以例項化的類上 { //獲取實體模型 Type type = model.GetType(); //獲取實體的所有屬性 foreach (PropertyInfo propertyInfo in type.GetProperties()) { //遍歷屬性,判斷屬性是否存在VerifyBaseAtttribute特性,true會去檢視繼承鏈是否存在 //這個之所以使用Base特性就是抽象出驗證特性共同的東西,所有驗證都可以呼叫這個方法生效 if (propertyInfo.IsDefined(typeof(VerifyBaseAtttribute), true)) { //然後獲取屬性上面的特性(注意一個屬性上面可能會出現多個驗證特性,所以這裡我們需要獲取一個數組) object[] atttribute = propertyInfo.GetCustomAttributes(typeof(VerifyBaseAtttribute), true); //進行遍歷 foreach (VerifyBaseAtttribute item in atttribute) { //呼叫驗證方法,傳入屬性的值 if (!item.Verify(propertyInfo.GetValue(model))) { //失敗返回 return false; } } } } return true; } }View Code
四、個人總結
1、首先特性相當於一個補充類(就是一個類),他可以在不該變使用者的前提下,給使用者新增欄位或者放法行為 好比我以前用過的攔截器 就是繼承了特性然後進行了拓展的,AOP思想.,我們將一些不相干的邏輯可以放到特性裡面,簡化我們主幹程式碼的邏輯和程式碼