一.特性是什么
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; } }
上面我們創建了一個返回備注字段的特性,注意特性就是一個類,當然就擁有類所有東西,我們定義一個有參構造函數就收傳過來的備注存在remarks屬性中,然后通過GetRemarks()方法返回備注信息,然后我們怎么使用呢?我們在剛才的枚舉中加入我們的特性,上面我們說過特性可以添加在枚舉上

/// <summary> /// 定義枚舉 /// </summary> public enum TypeEnum { /// <summary> /// 待支付 /// </summary> [Remark("待支付")] ToPay = 1, /// <summary> /// 支付成功 /// </summary> [Remark("支付成功")] PaymentSuccess = 2, /// <summary> /// 支付失敗 /// </summary> [Remark("支付失敗")] PaymentFailure = 3 }
好了我們現在准備工作都做好了,特性我們也寫了,使用特性的地方我們也加上了,但是我們要怎么獲取特性的信息呢?一臉懵逼了,別急兄弟們慢慢來。這里就要使用反射,反射的特點你們還記得嗎?它可以獲取某個類,獲取類的特性、屬性、字段等等,還可以獲取屬性的特性。(反射篇)

/// <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; } } }
上面是我們寫的一個枚舉的拓展方法,方便我們的使用。因為我們是在枚舉的字段使用特性,這里獲取調用方法的類,進行反射獲取里面的成員(這里我們是在字段上面使用特性,所以獲取枚舉在獲取里面的字段。)這樣是不是比我們以前這么多判斷更加方便簡潔呢?

class Program { static void Main(string[] args) { TypeEnum typeEnum = TypeEnum.PaymentFailure; Console.WriteLine(typeEnum.GetTypeName()); } }
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; } }

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

/// <summary> /// 驗證父類特性 /// 主要就是給驗證特性提供一個統一的入口 /// 實現方式:接口形式、抽象類形式進行重寫(這里我使用抽象類實現) /// </summary> public abstract class VerifyBaseAtttribute : Attribute { /// <summary> /// 統一驗證的入口 /// </summary> /// <returns></returns> public abstract bool Verify(object value); }
看了上面三段代碼你就會很疑惑為什么,你寫了一個抽象的特性?????滿臉問號等下就告訴你。

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; } }
四、個人總結
1、首先特性相當於一個補充類(就是一個類),他可以在不該變使用者的前提下,給使用者添加字段或者放法行為 好比我以前用過的攔截器 就是繼承了特性然后進行了拓展的,AOP思想.,我們將一些不相干的邏輯可以放到特性里面,簡化我們主干代碼的邏輯和代碼量。