C#編程(七十一)---------- 自定義特性


自定義特性

在說自定義之前,有必要先介紹一些基本的概念.

元數據:就是C#中封裝的一些類,無法修改,類成員的特性被稱為元數據中的注釋

 

1.什么是特性?

(1)屬性和特性的區別

屬性:屬性是面向對象思想里所說的封裝在類里面的數據字段,Get,Set方法.

 

特性:就相當於類的元數據.

 

來看看官方解釋?

特性是給指定的某一聲明的一則附加的聲明性信息。 允許類似關鍵字的描述聲明。它對程序中的元素進行標注,如類型、字段、方法、屬性等。從.net角度看,特性是一種 類,這些類繼承於System.Attribute類,用於對類、屬性、方法、事件等進行描述,主要用在反射中。但從面向對象的級別看,其實Attribute是類型級別的,而不是對象級別。

 

 

Attribute和.net文件的元數據保存在一起,可以用來向運行時描述你的代碼,或者在程序運行的時候影響程序的行為.

 

2.特性的應用

(1).net中特性用來處理多種問題,比如序列化,程序的安全特性,防止即時編譯器對程序代碼進行優化從而代碼容易調試等等.定值特性的本質上是在一個類的元素上添加附加信息,並在運行其通過反射得帶該附加信息(在使用數據實體對象時經常用到)

(2)Attribute作為編譯期的指令時的應用

Conditional起條件編譯的作用,只有滿足條件,才允許編譯器對它的代碼進行編譯.一般在程序調試的時候使用.

DllImport:用來標記非.net函數,表明該方法在一個外部的DLL定義.

Obsolete:這個屬性用來標記當前的方法已被廢棄,不再使用.

 

注意:Attribute是一個類,因此DllImport也是一個類,Attribute類是在編譯的時候實例化,而不是想通常那樣在運行時實例化.

CLSCompliant:保證整個程序集代碼遵守CLS,否則編譯將報錯.

 

3.自定義特性

使用AttributeUsage,來控制如何應用新定義的特性.

 

  [AttributeUsageAttribute(AttributeTargets.All  可以應用到任何元素

      ,AllowMultiple=true, 允許應用多次,我們的定值特性能否被重復放在同一個程序實體前多次。

      ,Inherited=false,不繼承到派生

        )]

特性也是一個類,必須繼承於System.Attribute類,命名規范我類名+Attribute.不管直接還是間接繼承,都會成為一個特性類,特性類的聲明定義了一種可以放置在聲明之上新的特性.使用[]語法使用自定義特性,可以使用反射來查看自定義特性.

案例:

public class MyselfAttribute:System.Attribute

 

不過說實話,特性確實常用到,但是自定義特性幾乎用不到,貌似老外喜歡用.

 

如果不能自己定義一個特性並使用它,我姓你肯定覺得我在忽悠你,假設我們有這樣一個很常見的需求:我們在創建或者更新一個類文件時,需要說要這個類時什么時候,由誰創建的,在一行的更新中還要說明在什么時候由誰更新的,可以記錄也可以不記錄更新的內容,以往的情況你會怎么做?肯定想到了添加注釋:

//更新:張三,2015-2-3,修改了ToString()方法

//更新:李四,2014-4-5

//創建:王五,2011-7-8

public class DemoClass

{

//dosomething

}

這樣做沒問題,想要啥,就寫啥,看起來很好啊,借用金星老師的一句話就是完美!

but,如果我們有一天想把這些記錄保存到數據庫中作為備份呢?你是不是要一條一條的去查看源文件,找出注釋,然后在一條條的插入到數據庫中呢?

 

通過上面特性的定義,我們知道特性可以用來給類型添加元數據(描述書覺得數據,包括數據是否被修改,何時創建,創建人,這些數據可以是一個類,方法,屬性),這些元數據可以用於描述類型.那么在此處,特性就會派上用場.那么在本例中,元數據應該是:注釋類型(更行或者創建),修改人,日期,備注信息(可有可無).而特性的目標類型是DemoClass類.

 

按照對於附加到DemoClass類上的元數據的理解,我們先創建一個封裝了元數據的類:

    public class RecordAttribute

    {

        private string recordType;//記錄類型:更新或者創建

        private string author;//作者

        private DateTime data;//日期

        private string memo;//備注

 

 

        //構造函數的參數在特性中也稱為"位置參數"

        public RecordAttribute(string  recordType,string author,string  date)

        {

            this.recordType = recordType;

            this.author = author;

            this.data = Convert.ToDateTime(date);

        }

 

        //對於位置參數,通常只提供get訪問

        public string RecordType { get { return recordType; } }

        public string Author { get { return author; } }

        public DateTime Date { get { return Date; } }

 

        

        //構建一個屬性,在特性中也叫"命名參數"

        public string Memo {

            get { return memo; }

            set { memo = value; }

        }

 

}

 

注意:構造函數的參數date,必須為一個常量,Type類型,或者是常量數組,所以不能直接傳遞DateTime類型.

你會說,這不就是一個類嗎?你不能因為后面跟了一個Attribute就搖身一變成了特性.那么這樣才能然他成為特性並應用到一個類上面呢?進行下一步之前,咱先來看看.net內置的特性Obsolete是如何定義的:

 

namespace System {  

    [Serializable]  

    [AttributeUsage(6140, Inherited = false)]  

    [ComVisible(true)]  

    public sealed class ObsoleteAttribute : Attribute {  

 

       public ObsoleteAttribute();  

       public ObsoleteAttribute(string message);  

       public ObsoleteAttribute(string message, bool error);  

 

       public bool IsError { get; }  

       public string Message { get; }  

    }  

}   

 

添加特性的格式(位置參數和命名參數)

首先,我們應該能夠發現,他繼承自Attribute,這說明我們的RecordAttribute也應該繼承自Attribute類.(一個特性類和普通類的區別是:繼承了Attribute類)

其次,我們發現在這個特性的定義上,又用了三個特性去描述他.這三個特性分別是:Serializable,AttributeUsage和ComVisible .  Serializable和ComVisible特性比較簡單,就是一個標志,AttributeUsage比較重要,有三個重要的參數可以設置,上面說了.

 

這里我們應該可以注意到:特性本身就是用來描述數據的元數據,而這三個特性又用來描述特性,所以他們是”元數據的元數據”(元元數據).

 

從這里我們可以看出,特性類本身也可以用除自身以外的其他特性來描述,所以這個特性類的特性數元元數據.

 

隱隱我們需要使用元元數據全無描述我們定義的特性RecordAttribute,所以現在我們需要首先了解一下”元元數據”.這里應該記得”元元數據”也是一個特性,大多數情況下,我們只需要掌握AttributeUsage就夠了.現在我們深入的研究一下它.先來看一下上面AttributeUsage是如何加載到ObsoleteAttribute特性上面的.

[AttributeUsage(6140,Inheritedfalse)]

看看AttributeUsage定義:

namespace System {  

    public sealed class AttributeUsageAttribute : Attribute {  

       public AttributeUsageAttribute(AttributeTargets validOn);  

 

       public bool AllowMultiple { get; set; }  

       public bool Inherited { get; set; }  

       public AttributeTargets ValidOn { get; }  

    }  

}

 

可以看到,頭一個構造函數,這個構造函數含有一個AttributeTargets類型的位置參數(Positional Parameter)validOn,還有兩個命名參數(Named Parameter).注意ValidOn屬性不是一個命名參數,因為他不包含set訪問器,是位置參數.

 

這里可能有有疑惑,為啥會這樣划分參數,這和特性的使用是相關的,加入AttributeUsageAttribute是一個普通的類,我們一定會這樣使用:

//實例化AttributeUsageAttribute類

AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class);

 

usage.AllowMultiple=true;//設置AllowMultiple屬性

usage.Inherited=false;//設置Inherited屬性

 

但是,特性只寫成一行代碼,然后緊靠其所應用的類型(目標類型),那么咋辦呢?大牛們想到了:不管是構造函數的參數還是屬性,彤彤寫到構造函數的圓括號中,對於構造函數的參數,必須按照構造函數參數的順序和類型;對於屬性,采用”屬性=值”這樣的格式,他們之間用逗號分隔.於是上面的代碼就縮減成了下面這樣:

[AttributeUsage(AttributeTargets.Class,AllowMutliple=true,Inherited=false)]

可以看出,AttributeTargets.Class是構造函數的參數(位置參數),而AllowMutliple和Inherited實際上是屬性(命名參數).命名參數是可選的.將來我們的RecordAttribute的使用方式於此相同.(為什么管這些屬性叫做作數,可能是因為它們的使用方式看上去更像方法的參數)

假設現在我們的RecordAttribute已經OK了,則它的使用使用應該是這樣的:

[RecordAttribute(“創建”,”syx”,”2015-8-8”,Memo=”hello,world”)]

public class DemoClass

{

//dosomething

}

其中recordType,author和date是位置參數,Memo是命名參數

 

C#自定義特性:AttributeTarget位標記

從AttributeUsage特性的名稱上可以看出它用於描述特性的使用方式.具體來說,首先應該是其所標記的特性可以應用於哪些類型或者對象.從上面的代碼中可以看到AttributeUsage特性的構造函數接受一個AttributeTargets類型的參數,那么我們現在就來了解一下AttributeTargets.

 

AttributeTargets是一個位標記,他定義了特性可以應用的類型和對象.

[Flags]

public enum AttributeTargets {

Assembly = 1,         //可以對程序集應用屬性。

Module = 2,              //可以對模塊應用屬性。

Class = 4,            //可以對類應用屬性。

Struct = 8,              //可以對結構應用屬性,即值類型。

Enum = 16,            //可以對枚舉應用屬性。

Constructor = 32,     //可以對構造函數應用屬性。

Method = 64,          //可以對方法應用屬性。

Property = 128,           //可以對屬性 (Property) 應用屬性 (Attribute)。

Field = 256,          //可以對字段應用屬性。

Event = 512,          //可以對事件應用屬性。

Interface = 1024,            //可以對接口應用屬性。

Parameter = 2048,            //可以對參數應用屬性。

Delegate = 4096,             //可以對委托應用屬性。

ReturnValue = 8192,             //可以對返回值應用屬性。

GenericParameter = 16384,    //可以對泛型參數應用屬性。

All = 32767,  //可以對任何應用程序元素應用屬性。

}

 

上述例子中使用的是:

[AttributeUsage(AttributeTargets.Class,AllowMutiple=true,Inherited=false)]

 

而ObsoleteAttribute特性中加載的AttributeUsage是這樣的:

[AttributeUsage(6140,Inherited=false)]

因為AttributeUsage是一個標記,所以可以使用按位或”|”來進行組合.so,當我們這樣寫的時候:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)

意味着既可以將特性應用到類上,也可以應用到接口上.

注意:這里存在這兩個特例,觀察上面的AttributeUsage的定義,說明特性還可以加載到程序集Assembly和模塊Module上,而這兩個屬於我們的編譯結果,在程序中並不存在這樣的類型,我們該如何加載呢?可以使用這樣的語法:[assembly:SomeAttribute(parameter list)],另外這條語句必須位於程序語句開始之前。

 

 

C#自定義特性:實現RecordAttribute

現在實現RecordAttribute應該很輕松了,對於類的主題不需要進行任何的修改,我們只是需要讓這個類繼承自Attribute類,同時使用AttributeUsage特性標記一下它就可以了(假定我們希望可以對類和方法應用此特性):

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple=true,Inherited=false)]

public class recordAttribute:Attribute

{

//主體

}

 

完整代碼:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace 自定義特性

{

    class Program

    {

        static void Main(string[] args)

        {

            DemoClass demo = new DemoClass();

            Console.WriteLine(demo.ToString());

            Console.ReadKey();

        }

    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]

    public class RecordAttribute : Attribute

    {

        private string recordType;//記錄類型:更新或者創建

        private string author;//作者

        private DateTime data;//日期

        private string memo;//備注

 

 

        //構造函數的參數在特性中也稱為"位置參數"

        public RecordAttribute(string recordType, string author, string date)

        {

            this.recordType = recordType;

            this.author = author;

            this.data = Convert.ToDateTime(date);

        }

 

        //對於位置參數,通常只提供get訪問

        public string RecordType { get { return recordType; } }

        public string Author { get { return author; } }

        public DateTime Date { get { return Date; } }

 

 

        //構建一個屬性,在特性中也叫"命名參數"

        public string Memo

        {

            get { return memo; }

            set { memo = value; }

        }

    }

    [Record("更新", "wangwu", "2008-1-20", Memo = "修改 ToString()方法")]

    [Record("更新", "lisi", "2008-1-18")]

    [Record("創建", "zhangsan", "2008-1-15")] 

    public class DemoClass

    {

        public override string ToString()

        {

            return "hello,world!";

        }

    }

 

}

 

 

這段程序可能簡單的輸出”hello,world”.我們的屬性也好像使用”//”來注釋一樣對程序沒有任何影響,實際上,我們添加的數據已經作為元數據添加到程序集中.

至此,一個完整的自定義特性的使用已經完成了,舉個例子幫助你理解特性打個比方:你約一個沒見過面的網友約會,約好時間地點,怎么解決不認識TA的問題?你們可以約好,手上拿個特別的東西不就解決了。這個特別的、用於標識你所不認識的人的東西,就相當於Attribute了。所以Attribute是用於在運行期動態調用的場合。

如果僅僅是前面介紹的內容,還是不足以說明Attribute有什么實用價值的話,那么從后面的章節開始我們將介紹幾個Attribute的不同用法,相信你一定會對Attribute有一個新的了解。

 


免責聲明!

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



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