1. 程式人生 > >C#的特性Attribute

C#的特性Attribute

一、什麼是特性

  特性是用於在執行時傳遞程式中各種元素(比如類、方法、結構、列舉、元件等)的行為資訊的宣告性標籤,這個標籤可以有多個。您可以通過使用特性向程式新增宣告性資訊。一個宣告性標籤是通過放置在它所應用的元素前面的方括號([ ])來描述的。

  特性可以描述我們的程式碼,或者影響應用程式的行為。特性可以用來處理多種問題,比如序列化、資料驗證、程式的安全特徵等等。

  特性不是修飾符而是一個有獨特例項化形式的類,繼承於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);
            }
        }
    }

  執行可以看到結果如下:

四、結尾

  通過上述的例子可以看出特性可以和反射配合來進行相應的操作,不過反射會消耗效能,並且特性類可以用別的特性描述。

  如果有什麼問題可以留言討論!謝謝閱讀。