1. 程式人生 > >C#中的Attribute詳解(上)

C#中的Attribute詳解(上)

近期正在研究AOP,本以為學會之後就又得了一個寶貝,想想心中還挺高興的。我在學習時無意中發現了一位大牛在12年前寫的一篇關於AOP的部落格(http://www.cnblogs.com/wayfarer/articles/241024.html),寫的真是很深入很不錯,這時我突然感覺自己很渺小很無知,人家12年前就瞭如指掌的東西,我到如今還在東拼西湊地找學習資料,覺得自己真是太差勁了。不過後來我也想通了,聞道有先後,術業有專攻,只要自己不斷地努力,學習並快樂著,掌握的知識越來越豐富,技藝越來越精湛,那自己的結果也不會差,有點扯多了……
那AOP跟這裡講的Attribute有什麼關係呢?AOP中的動態代理就是利用Attribute實現的,所以要想搞清楚動態代理,先要弄明白Attribute,因此就有了這篇文章。

一、Attribute是什麼

Attribute是一種可由使用者自有定義的修飾符(Modifier),可以用來修飾各種需要被修飾的目標,修飾符(比如private、public、static、override、virtual等等)是C#語言本身的關鍵字。
簡單地說,Attribute就是一種“附著物”——就像牡蠣吸附在船底或礁石上一樣。
這些附著物的作用是為它們的附著體追加上一些額外的資訊(這些資訊儲存在附著物的體內)——比如“這個類是我寫的”或者“這個函式以前出過問題”等等。

二、Attribute的作用

Attribute的作用是為元資料新增內容。
元資料可以被工具支援,比如:編譯器用元資料來輔助編譯,偵錯程式用元資料來除錯程式。

三、Attribute與註釋的區別

註釋是對程式原始碼的一種說明,主要目的是給人看的,在程式被編譯的時候會被編譯器所丟棄,因此,它絲毫不會影響到程式的執行。
Attribute是程式程式碼的一部分,它不但不會被編譯器丟棄,而且還會被編譯器編譯程序序集(Assembly)的元資料(Metadata)裡。在程式執行的時候,你隨時可以從元資料中提取提取出這些附加資訊,並以之決策程式的執行。

四、系統Attribute範例

在專案中,某個類由兩個程式設計師(小張和小李)共同維護。這個類起到了“工具包”(Utilities)的作用,裡面包含幾十個靜態方法,就像.Net Framework中的Math類一樣。這些靜態方法,一半是小張寫的、一半是小李寫的;在專案的測試中,有一些靜態方法曾經出過bug,後來又被修正。
我們可以把這些方法分成這樣幾類:
這裡寫圖片描述


我們分類的目的主要是在測試的時候可以按照不同的類別進行測試、獲取不同的效果。比如:統計兩個人的工作量或者對曾經出過bug的方法進行迴歸測試。

1、如果不使用Attribute,為了區分這四類靜態方法,我們只能通過註釋來說明,但這樣做會給系統帶來很多垃圾資訊,而且不利於測試。註釋區分法如下:

public static void Li_Buged_FuncA(){ }
public static void Li_NoBug_FuncB(){ }
public static void Zhang_Buged_FuncC(){ }
public static void Zhang_NoBug_FuncD(){ }

或

//Created by Li,Buged
public static void FuncA(){ }
//Created by Li,NoBug
public static void FuncB(){ }
//Created by Zhang,Buged
public static void FuncC(){ }
//Created by Zhang,NoBug
public static void FuncD(){ }

2、如果使用Attribute,區分這四類靜態方法將會簡單許多。當類Program和類ToolKit都在Program.cs檔案中時,#define Buged只需要在Program.cs檔案頭部定義,示例程式碼如下:

#define Buged //C#的巨集定義必須在所有程式碼之前。當前只讓Buged巨集有效
//#define NoBug
//#define Li
//#define Zhang
using System;
using System.Diagnostics;//注意:ConditionalAttribute特性包含在此名稱空間中

namespace AttributeTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ToolKit.FuncA();
            ToolKit.FuncB();
            ToolKit.FuncC();
            ToolKit.FuncD();
            Console.ReadKey();
        }
    }

    public class ToolKit
    {
        [ConditionalAttribute("Li")]//Attribute名稱的長記法
        [ConditionalAttribute("Buged")]
        public static void FuncA()
        {
            Console.WriteLine("Created by Li, Buged");
        }

        [Conditional("Li")]//Attribute名稱的短記法
        [Conditional("NoBug")]
        public static void FuncB()
        {
            Console.WriteLine("Created by Li, NoBug");
        }

        [Conditional("Zhang")]
        [Conditional("Buged")]
        public static void FuncC()
        {
            Console.WriteLine("Created by Zhang, Buged");
        }

        [Conditional("Zhang")]
        [Conditional("NoBug")]
        public static void FuncD()
        {
            Console.WriteLine("Created by Zhang, NoBug");
        }
    }
}

當類Program在Program.cs檔案中,類ToolKit在ToolKit.cs檔案中時,#define Buged需要在Program.cs檔案頭部和ToolKit.cs檔案頭部均定義,示例程式碼如下:

#define Buged //C#的巨集定義必須在所有程式碼之前。當前只讓Buged巨集有效
//#define NoBug
//#define Li
//#define Zhang
using System;
using System.Diagnostics;
namespace AttributeTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ToolKit.FuncA();
            ToolKit.FuncB();
            ToolKit.FuncC();
            ToolKit.FuncD();
            Console.ReadKey();
        }
    }
}

#define Buged //C#的巨集定義必須在所有程式碼之前。當前只讓Buged巨集有效
//#define NoBug
//#define Li
//#define Zhang
using System;
using System.Diagnostics;
namespace AttributeTest
{
    public class ToolKit
    {
        [Conditional("Li")]
        [Conditional("Buged")]
        public static void FuncA()
        {
            Console.WriteLine("Created by Li, Buged");
        }

        [Conditional("Li")]
        [Conditional("NoBug")]
        public static void FuncB()
        {
            Console.WriteLine("Created by Li, NoBug");
        }

        [Conditional("Zhang")]
        [Conditional("Buged")]
        public static void FuncC()
        {
            Console.WriteLine("Created by Zhang, Buged");
        }

        [Conditional("Zhang")]
        [Conditional("NoBug")]
        public static void FuncD()
        {
            Console.WriteLine("Created by Zhang, NoBug");
        }
    }
}

執行結果如下:
這裡寫圖片描述
注意:執行結果是由程式碼中“#define Buged ”這個巨集定義所決定。

五、系統Attribute範例分析

1、在本例中,我們使用了ConditionalAttribute這個Attribute,它被包含在System.Diagnostics名稱空間中,多半時間是用來做程式除錯與診斷的。
2、與ConditionalAttribute相關的是一組C#巨集,它們看起來與C語言的巨集別無二致,位置必須在所有C#程式碼之前。顧名思義,ConditionalAttribute是用來判斷條件的,凡被ConditionalAttribute(或Conditional)”附著”了的方法,只有滿足了條件才會執行。
3、就像船底上可以附著很多牡蠣一樣,一個方法上也可以附著多個ConditionalAttribute的例項。把Attribute附著在目標上的書寫格式很簡單,使用方括號把Attribute括起來,然後緊接著寫Attribute的附著體即可。當多個Attribute附著在同一個目標上時,就把這些Attribute的方括號一個挨一個地書寫(或者在一對方括號中書寫多個Attribute),而且不必在乎它們的順序。
4、在使用Attribute的時候,有“長記法”和“短記法”兩種。

由上面的第三條和第四條我們可以推出,以下四種Attribute的使用方式完全等價:

//長記法
[ConditionalAttribute("Li")]
[ConditionalAttribute("NoBug")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
//短記法
[Conditional("Li")]
[Conditional("NoBug")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
//換序
[Conditional("NoBug")]
[Conditional("Li")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }
//單括號疊加
[Conditional("NoBug"),Conditional("Li")]
public static void Func()
{Console.WriteLine("Created by Li, NoBug"); }