【.net 深呼吸】自定義特性(Attribute)的實現與檢索方法


在.net的各個語言中,尤其是VB.NET和C#,都有特性這一東東,具體的概念,大家可以網上查,這里老周說一個非標准的概念——特性者,就是對象的附加數據。對象自然可以是類型、類型成員,以及程序集。

說簡單點,就是你在定義一些代碼時,希望為某個代碼對象加上一些額外的內容,但這些內容又不便在代碼中直接寫。比如,你為B類定義了一個 int 類型的屬性P,而且是個虛屬性,就是B的派生類可以重寫它。我希望可以給這個屬性弄個版本號,當子類override這個屬性時,給它記一個版本號,然后在其他代碼中訪問這個屬性時,可以檢查一下版本號,比如當版本號小於3時,拋出異常,不讓使用。

另外比如,當某個類的某個成員在后續版本中會刪除時,也可以使用特性來附加一個說明,好讓調用代碼識別,在.net類庫中常用這種方法。在比如,可以給某些對象加上免調試的特性,當調用代碼時,遇到帶有這些特性的對象時就跳過調試。

 

除了API庫給出的特性外(特性類通常以Attribute結尾),我們也可以根據需要,自己定義特性類。

自定義特性的定義和一般類的定義差不多,不過要注意兩點:一是應該從Attribute類或其子類派生,一般是直接從Attribute類派生;二是,不管是不是直接派生自Attribute類,只要是特性類,都要在該類的定義上附加AttributeUsageAttribute特性,作用是指明你定義的這個特性的應用范圍。

啥意思呢,就是你這個特性能用在哪些對象上,比如這個特性只能用在方法上,那就把AttributeTargets值設為Method,如果只希望特性只用在類上,就指定Class;要是希望特性可以同時用在屬性和方法上,就把Method和Property值組合起來。如果打算讓這個特性成為“萬能”通,那就用值All,表示特性可以用在所有代碼對象上。

 

下面,還是舉個例子吧。假設我定義了這個特性。別管它干嗎用的,反正只是個示例。

    [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
    public class AssRateAttribute : Attribute
    {
        ushort m_rate;
        public AssRateAttribute(ushort rate)
        {
            m_rate = rate;
        }

        public ushort Rate
        {
            get { return m_rate; }
        }
    }

這里我指定了,它只能用於程序集,為啥選的程序集呢,因為它比較特殊,待會兒老周順便介紹一下如何給程序集附加特性。

 

還有兩個屬性,一個是Inherited,就是說這個特性是否可以被繼承,當然,對於程序集而言,無所謂。不過,如果這個特性是用在類、類型成員上,就可以用上,我把這個特性用在某對象,它的派生類是否繼承這個特性。要是設置為false,那么這個特性只有附加的目標類型上可以檢索到,而在類型的派生類上是檢索不到的。

AllowMultiple指定該特性是否可以在同一個對象上多次使用。如果為false,那在附加時只能用一次,比如,下面的用法是會報錯的。

[AssRate(5)]
[AssRate(9)]
public class C
{

}

因為上面的C類上,AssRateAttribute特性用了兩次,就會出錯。如果希望它可以多個實例並用,就把AllowMultiple屬性改為true。當然,上面的代碼只是假設,因為AssRateAttribute是不能用在類上的,它已指明只能用在程序集上。

 

接下來,看看如何把特性貼到程序集上,程序集的特性用法比較特殊,因為它沒有實體代碼,只是個邏輯組合,因此,你聽好了:請把用於程序集的特性,寫在所有代碼的前面

就是說,如果你的代碼文件前面寫了代碼,那就不能再為程序集加特性了。例如這樣是不對的。

namespace Test{
       ....
        public class DocumentsOpt{  .... }
}

[assembly:AssRate(12)]

這樣做是錯的,因為前面已經有代碼了,再為程序集附加特性是不可以的。

正確的do法是這樣的:

[assembly:AssRate(12)]

using System;
using System.IO;
namespace Test{ .... public class DocumentsOpt{ .... } }

 

特性前面的assembly: 是啥意思呢,其實,特性的完整語法應該如下:

[ <target:> <attributes....> ]

例如要把特性用在類上,可以寫上:

[class: MyAttr ]

 

不過,對於類,我們一般是可以省掉target,因為我們會在類的定義前面寫上特性,這樣編譯器也能識別出來是附加到類上的。

 

根據老周耍.net的多年經驗,只有兩種情況下才要寫上target,其余情況可以省略。

1、方法的返回值,因為這個特性雖然用在方法的返回值上,但是它只能寫在方法的定義前,為了告訴編譯器,這個特性不是給方法的,是給返回值的,所以要寫上return,就像這樣:[return: Attris...]。

2、程序集,因為程序集是邏輯性的,不存在實體的代碼對象,只能明確指定特性是用在程序集上的,即[assembly: MyAttr]。

 

那么如何確保用於程序集的特性都能在所有代碼之前呢,VS在項目中弄了這個結構,與項目本身有關的,都放在Properties節點下,其中有個代碼文件叫AssemblyInfo.cs,或者AssemblyInfo.vb,這個文件不寫其他的代碼,只用來放程序集的特性。就像這樣:

[assembly: AssemblyTitle("app")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("app")]
[assembly: AssemblyCopyright("Copyright © 老周 2016")]

 

所以,我們剛剛定義的AssRateAttribute特性就要寫在這個文件里。

[assembly: AssRate(4)]

為什么特性類要以Attribute結尾,除了便於識別,也看到在代碼中使用時,可以省略Attribute。

 

那么,特性應用之后,我們在運行階段如何檢索呢,這個嘛,你想啊,要動態知道對象的結構和信息,該用啥技術?對了,反射。就像你照鏡子那樣,可以把你的嘴臉呈現在鏡子中,然后你就能看到自己了,道理一樣,反射就是給代碼照鏡子用的,你可以運行時知道代碼的結構,盡管代碼已經編譯了。

比如下面的方法,檢測我們剛定義的特性,看看特性指定的Rate值,然后做出響應。

        static void Check()
        {
            Assembly currassembly = Assembly.GetExecutingAssembly();
            AssRateAttribute att = currassembly.GetCustomAttribute<AssRateAttribute>();
            if (att != null)
            {
                if (att.Rate <6)
                {
                    throw new InvalidOperationException("程序集人品分太低,禁止使用。");
                }
                else
                {
                    Console.WriteLine("程序集人品分合格。");
                }
            }
        }

反射API定義了多個GetCustomAttribute或,GetCustomAttributes方法,用來檢索特性實例,這些方法包括多種擴展方法,我就不一一介紹,自己查MSDN和對象瀏覽器。如果在定義特性類時指明了它允許多個實例,就用GetCustomAttributes方法來獲取多個實例,如果AllowMultiple為False,只允許單一實例,就用GetCustomAttribute方法獲取單個特性實例。獲取到特性實例后就可以進行驗證或處理了。

 

好,本文內容就扯到這里了。哦,關於示例源代碼嘛,不提供,想玩的話,自己動手寫。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM