一、什么是特性
特性是用於在運行時傳遞程序中各種元素(比如類、方法、結構、枚舉、組件等)的行為信息的聲明性標簽,這個標簽可以有多個。您可以通過使用特性向程序添加聲明性信息。一個聲明性標簽是通過放置在它所應用的元素前面的方括號([ ])來描述的。
特性可以描述我們的代碼,或者影響應用程序的行為。特性可以用來處理多種問題,比如序列化、數據驗證、程序的安全特征等等。
特性不是修飾符而是一個有獨特實例化形式的類,繼承於Attributes基類。其實我們在很多地方都能接觸到特性,特性在平時的運用中是非常常見的,比如以下三個場景:
1.特性[Serializable]標記可序列化的類
[Serializable] public class MyObject { }
2.特性[ServiceContract]指名WCF中可以用來對外調用的接口
[ServiceContract] public interface IService{}
3.特性[Range]用於MVC中類的屬性的范圍
[Range(18, 60)] public int Age { get; set; }//年齡范圍
二、預定義特性
.Net框架已經給我們提供了一些預定義的特性,像是上面的三個場景的三個特性我們就可以直接拿來用。這里我們主要介紹另外三個比較基礎的特性,它們都繼承Attribute類,分別是:Obsolete、Conditional和AttributeUsage。
1.Obsolete
這個預定義特性標記了不應被使用的程序實體。它可以讓您通知編譯器丟棄某個特定的目標元素。例如,當一個新方法被用在一個類中,但是您仍然想要保持類中的舊方法,您可以通過顯示一個應該使用新方法,而不是舊方法的消息,來把它標記為 obsolete(過時的)。
- 參數 message,是一個字符串,描述項目為什么過時的原因以及該替代使用什么。
- 參數 iserror,是一個布爾值。如果該值為 true,編譯器應把該項目的使用當作一個錯誤。默認值是 false(編譯器生成一個警告)。
示例如下:
[Obsolete("該方法已經過時,用NewMethod代替", true)] public static void OldMethod() { Console.WriteLine("OldMethod"); }
2.Conditional
Conditional標記了一個條件方法,當滿足謀個條件的時候該方法才能執行,多用於程序的調試和診斷。
示例如下:
#define Error //宏定義,決定那個方法執行 using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace Attribute.Demo { class Program { static void Main(string[] args) { Debug(); Error(); Console.ReadKey(); } [Conditional("Debug")] public static void Debug() { Console.WriteLine("Debug"); } [Conditional("Error")] public static void Error() { Console.WriteLine("Error"); } } }
最后結果是:
Error
3.AttributeUsage
預定義特性 AttributeUsage 描述了如何使用一個自定義特性類。它規定了自定義特性可應用到的項目的類型。這說明了該特性可以描述別的特性,對描述的特性進行某些規定。
- 參數 validon 規定特性可被放置的語言元素。它是枚舉器 AttributeTargets 的值的組合。默認值是 AttributeTargets.All。
- 參數 allowmultiple(可選的)為該特性的 AllowMultiple 屬性(property)提供一個布爾值。如果為 true,則該特性是多用的。默認值是 false(單用的)。
- 參數 inherited(可選的)為該特性的 Inherited 屬性(property)提供一個布爾值。如果為 true,則該特性可被派生類繼承。默認值是 false(不被繼承)。
示例如下:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
可以規定多個可放置的語言元素,用標識符 | 分隔開來就行,上述代碼就表示描述的特性可以用於屬性和字段,如果標注在別的如類上就會報錯。
三、自定義特性
前面有提到預定義的特性都有繼承自定義特性的基類Attribute,那么我們自己實現一個自定義特性也就需要繼承Attribute類。那突然想到既然特性是一個類,那么為什么直接在描述目標前用方括號聲明特性就可以又和一般的類有什么區別呢?主要有以下的一些區別和注意點:
- 特性的實例化不是通過new的,而是在方括號中調用構造函數。並且構造函數可以有多個,構造函數里的參數為定位參數,定位參數必須放在括號的最前面,按照傳入的定位參數可以調用相應的構造函數來實例化,如果有自己定義的構造函數則必須傳入定位參數進行實例化否則報錯。
- 特性中屬性的賦值,可以通過具名參數賦值,但是具名參數必須在定位參數后面,順序可以打亂的,具體的形式如ErrorMessage = "年齡不在規定范圍內"。
接下來我就來自己實現驗證屬性值是否在規定區間內的特性,類似於[Range]。我們定義一個特性MyRangeAttribute繼承基類Attribute,用預定義特性AttributeUsage規定只能用於描述屬性,並自定義構造函數傳入最小值和最大值,並定義方法Validate()校驗,具體如下:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] class MyRangeAttribute : System.Attribute { public MyRangeAttribute(int _min, int _max) { this.max = _max; this.min = _min; } private int max; public int Max { get; set; } private int min; public int Min { get; set; } private string errorMessage; public string ErrorMessage { get; set; } public bool Validate(int _value) { return _value >= min && _value <= max; } }
接下來,我們創建一個Student類里面有Age屬性並用我們的自定義特性MyRangeAttribute描述,Student類繼承People類,在People類中有方法IsValidate()通過反射執行特性的校驗方法Validate(),具體如下:
class Student: BaseClass { private int age; [MyRange(0,10, ErrorMessage = "年齡不在規定范圍內")] public int Age { get;set; } } class BaseClass { public bool IsValidate(out string msg) { msg = string.Empty; Type type = this.GetType(); foreach (var prop in type.GetProperties()) { foreach (var attribute in prop.GetCustomAttributes()) { object[] parameters = new object[] { (int)prop.GetValue(this, null) }; if ((bool)attribute.GetType().GetMethod("Validate").Invoke(attribute, parameters)) return true; else { msg = attribute.GetType().GetProperty("ErrorMessage").GetValue(attribute,null).ToString(); return false; } } } return false; } }
我們在控制台程序中執行如下代碼:
static void Main(string[] args) { string msg = string.Empty; Student student = new Student(); while (true) { Console.WriteLine("請輸入年齡(輸入exit退出):"); string str = Console.ReadLine(); if (str.Equals("exit")) break; else { student.Age = Convert.ToInt32(str); if (student.IsValidate(out msg)) Console.WriteLine("驗證通過"); else Console.WriteLine(msg); } } }
運行可以看到結果如下:
四、結尾
通過上述的例子可以看出特性可以和反射配合來進行相應的操作,不過反射會消耗性能,並且特性類可以用別的特性描述。
如果有什么問題可以留言討論!謝謝閱讀。