1. 程式人生 > >C#圖解教程 第二十四章 反射和特性

C#圖解教程 第二十四章 反射和特性

疑問 兩個 全局 代碼行數 owin info 技術 ole jpg

反射和特性
元數據和反射
Type 類
獲取Type對象
什麽是特性
應用特性
預定義的保留的特性
Obsolete(廢棄)特性
Conditional特性
調用者信息特性
DebuggerStepThrough 特性
其他預定義特性
有關應用特性的更多內容
多個特性
其他類型的目標
全局特性
自定義特性
聲明自定義特性
使用特性的構造函數
指定構造函數
使用構造函數
構造函數中的位置參數和命名參數
限制特性的使用
自定義特性的最佳實踐
訪問特性
使用IsDefined方法
使用GetCustomAttributes方法

Note

  • 類的元數據包含該類的成員和特性
  • 程序的元數據可以理解為程序的結構信息
  • 反射(reflection)用來查看元數據
  • C#中通過Type類來反射
  • 特性(attribute)用來給類型添加元數據

PS:理解有待加強


反射和特性

元數據和反射


大多數程序都要處理數據,包括讀、寫、操作和顯示數據。(圖形也是一種數據的形式。)然而,對於某些程序來說,它們操作的數據不是數字、文本或圖形,而是程序和程序類型本身的信息。

  • 有關程序及其類型的數據被稱為元數據(metadata),它們保存在程序的程序集中
  • 程序在運行時,可以查看其他程序集或其本身的元數據。一個運行的程序査看本身的元數據或其他程序的元數據的行為叫做反射(reflection)

對象瀏覽器是顯式元數據的程序的一個示例。它可以讀取程序集,然後顯示所包含的類型以及類型的所有特性和成員。
本章將介紹程序如何使用Type類來反射數據,以及程序員如何使用特性來給類型添加元數據。

要使用反射,我們必須使用System.Reflection命名空間。

Type 類


之前已經介紹了如何聲明和使用C#中的類型。包括預定義類型(int、long和string等)、BCL中的類型(Console、IEnumerable等)以及用戶自定義類型(MyClass、Mydel等)。每一種類型都有自己的成員和特性。
BCL聲明了一個叫做Type的抽象類,它被設計用來包含類型的特性。使用這個類的對象能讓我們獲取程序使用的類型的信息。
由於Type是抽象類,因此它不能有實例。而是在運行時,CLR創建從Type(RuntimeType)派生的類的實例,Type包含了類型信息。當我們要訪問這些實例時,CLR不會返回派生類的引用而是Type基類的引用。但是,為了簡單起見,在本章剩余的篇幅中,我會把引用所指向的對象稱為Type類型的對象(雖然從技術角度來說是一個BCL內部的派生類型的對象)。
需要了解的有關Type的重要事項如下:

  • 對於程序中用到的每一個類型,CLR都會創建一個包含這個類型信息的Type類型的對象
  • 程序中用到的每一個類型都會關聯到獨立的Type類的對象
  • 不管創建的類型有多少個實例,只有一個Type對象會關聯到所有這些實例

下圖顯示了一個運行的程序,它有兩個MyClass對象和一個OtherClass對象。註意,盡管有兩個MyClass的實例,只會有一個Type對象來表示它。
技術分享
我們可以從Type對象中獲取需要了解的有關類型的幾乎所有信息。下表列出了類中更有用的成員。
技術分享

獲取Type對象


本節學習使用GetType方法和typeof運算符來獲取Type對象。object類型包含了一個叫做GetType的方法,它返回對實例的Type對象的引用。由於每一個類型最終都是從object繼承的,所以我們可以在任何類型對象上使用GetType方法來獲取它的Type對象,如下所示:
Type t = myInstance.GetType();
下面的代碼演示了如何聲明一個基類以及從它派生的子類。Main方法創建了每一個類的實例並且把這些引用放在了一個叫做bca的數組中以方便使用。在外層的foreach循環中,代碼得到了Type對象並且輸出類的名字,然後獲取類的字段並輸出。下圖演示了內存中的對象。
技術分享

using System;
using System.Reflection;
class BaseClass
{
    public int BaseField=0;
}
class DerivedClass:BaseClass
{
    public int DerivedField=0;
}
class Program
{
    static void Main()
    {
        var bc=new BaseClass();
        var dc=new DerivedClass();
        BaseClass[] bca=new BaseClass[]{bc,dc};

        foreach(var v in bca)
        {
            Type t=v.GetType();

            Console.WriteLine("Object type : {0}",t.Name);

            FieldInfo[] fi=t.GetFields();
            foreach(var f in fi)
            {
                Console.WriteLine("    Field : {0}",f.Name);
            }
            Console.WriteLine();
        }
    }
}

技術分享
我們還可以使用typeof運算符來獲取Type對象。只需要提供類型名作為操作數,它就會返回Type對象的引用,如下所示:

Type t = typeof(DerivedClass);
            ↑        ↑
         運算符 希望的Type對象的類型

下面的代碼給出了一個使用typeof運算符的簡單示例:

using System;
using System.Reflection;

namespace SimpleReflection
{
    class BaseClass
    {
        public int MyFieldBase;
    }
    class DerivedClass:BaseClass
    {
        public int MyFieldDerived;
    }
    class Program
    {
        static void Main()
        {
            Type tbc=typeof(DerivedClass);
            Console.WriteLine("Result is {0}.",tbc.Name);

            Console.WriteLine("It has the following fields:");
            FieldInfo[] fi=tbc.GetFields();
            foreach(var f in fi)
            {
                Console.WriteLine("    {0}",f.Name);
            }
        }
    }
}

技術分享

什麽是特性

特性(attribute)是一種允許我們向程序的程序集增加元數據的語言結構。它是用於保存程序結構信息的某種特殊類型的類。

  • 將應用了特性的程序結構(program construct)叫做目標(target)
  • 設計用來獲取和使用元數據的程序(比如對象瀏覽器)叫做特性的消費者(consumer)
  • .NET預定了很多特性,我們也可以聲明自定義特性

下圖是使用特性中相關組件的概覽,並且也演示了如下有關特性的要點。

  • 我們在源代碼中將特性應用於程序結構
  • 編譯器獲取源代碼並且從特性產生元數據,然後把元數據放到程序集中
  • 消費者程序可以獲取特性的元數據以及程序中其他組件的元數據。註意,編譯器同時生產和消費特性

技術分享

根據慣例,特性名使用Pascal命名法並且以Attribute後綴結尾。當為目標應用特性時,我們可以不使用後綴。例如,對於SerializableAttribute和MyAttributeAttribute這兩個特性,我們在把它們應用到結構時可以使用Serializable和MyAttribute短名稱。

應用特性


我們先不講解如何創建特性,而是看看如何使用已定義的特性。這樣,你會對它們的使用情況有個大致了解。
特性的目的是告訴編譯器把程序結構的某組元數據嵌入程序集。我們可以通過把特性應用到結構來實現。

  • 在結構前放置特性片段來應用特性
  • 特性片段被方括號包圍,其中是特性名和特性的參數列表

例如,下面的代碼演示了兩個類的開始部分。最初的幾行代碼演示了把一個叫做Serializable的特性應用到MyClass。註意,Serializable沒有參數列表。第二個類的聲明有一個叫做MyAttribute的特性,它有一個帶有兩個string參數的參數列表。

[Serializable]
public class MyClass
{
    …
}
[MyAttribute("Simple class","Version 3.57")]
public class MyOtherClass
{
    …
}

有關特性需要了解的重要事項如下:

  • 大多數特性只針對直接跟隨在一個或多個特性片段後的結構
  • 應用了特性的結構稱為被特件裝飾(decorated或adorned,兩者都應用得很普遍)

預定義的保留的特性


在學習如何定義自己的特性之前,本小節會先介紹幾個.NET預定義特性。

Obsolete(廢棄)特性

一個程序可能在其生命周期中經歷多次發布,而且很可能延續多年。在程序生命周期的後半部分,程序員經常需要編寫類似功能的新方法替換老方法。出於多種原因,你可能不想再使用那些調用過時的舊方法的老代碼,而只想用新編寫的代碼調用新方法。
如果出現這種情況,你肯定希望稍後操作代碼的團隊成員或程序員也只使用新代碼。要警告他們不要使用舊方法,可以使用Obsolete特性將程序結構標註為過期的,並且在代碼編譯時顯式有用的警告消息。以下代碼給出了一個使用的示例:

class Program
{
    //應用特性
    [Obsolete("User method SuperPrintOut")]
    static void PrintOut(string str)
    {
        Console.WriteLine(str);
    }
    static void Main(string[] args)
    {
        PrintOut("Start of Main");
    }
}

註意,即使PrintOut被標註為過期,Main方法還是調用了它。代碼編譯也運行得很好並且產生了如下的輸出:
技術分享
不過,在編譯的過程中,編譯器產生了下面的CS0618警告消息來通知我們正在使用一個過期的結構:
技術分享
另外一個Obsolete特性的重載接受了bool類型的第二個參數。這個參數指定目標是否應該被標記為錯誤而不僅僅是瞥告。以下代碼指定了它需要被標記為錯誤:

                                   標記為錯誤
                                       ↓
[Obsolete("User method SuperPrintOut",true)]
static void PrintOut(string str)
{
    …
}

Conditional特性

Note
Conditional特性類似於C語言的條件編譯

Conditional特性允許我們包括或排斥特定方法的所有調用。為方法聲明應用Conditional特性並把編譯符作為參數來使用。

  • 如果定義了編譯符號,那麽編譯器會包含所有調用這個方法的代碼,這和普通方法沒有什麽區別
  • 如果沒有定義編譯符號,那麽編譯器會忽略代碼中這個方法的所有調用

定義方法的CIL代碼本身總是會包含在程序集中。只是調用代碼會被插入或忽略。
例如,在如下的代碼中,把Conditional特性應用到對一個叫做TraceMessage的方法的聲明上。特性只有一個參數,在這裏是字符串DoTrace。

  • 當編譯器編譯這段代碼時,它會檢査是否有一個編譯符號被定義為DoTrace
  • 如果DoTrace被定義,編譯器就會像往常一樣包含所有對TraceMessage方法的調用
  • 如果沒有DoTrace這樣的編譯符號被定義,編譯器就不會輸出任何對TraceMessage的調用
[Conditional("DoTrace")]
static void TraceMessage(string str)
{
    Console.WriteLine(str);
}

Conditional特性的示例
以下代碼演示了一個使用Conditional特性的完整示例。

  • Main方法包含了兩個對TraceMessage方法的調用
  • TraceMessage方法的聲明被用Conditional特性裝飾,它帶有DoTrace編譯符號作為參數。因此,如果DoTrace被定義,那麽編譯器就會包舍所有對TraceMessage的調用代碼
  • 由於代碼的第一行定義了叫做DoTrace的編譯符,編譯器會包含兩個對TraceMessage的調用
#define DoTrace
using System;
using System.Diagnostics;
namespace AttributeConditional
{
    class Program
    {
        [Conditional("DoTrace")]
        static void TraceMessage(string str)
        {
            Console.WriteLine(str);
        }
        static void Main()
        {
            TraceMessage("Start of Main");
            Console.WriteLine("Doing work in Main.");
            TraceMessage("End of Main");
        }
    }
}

技術分享
如果註釋掉第一行來取消DoTrace的定義,編譯器就不再會插人兩次對TraceMessage的調用代碼。這次,如果我們運行程序,就會產生如下輸出:
技術分享

調用者信息特性

調用者信息特性可以訪問文件路徑、代碼行數、調用成員的名稱等源代碼信息。

  • 這三個特性名稱為CallerFilePathCallerLineNumberCallerMemberName
  • 這些特性只能用於方法中的可選參數

下面的代碼聲明了一個名為MyTrace的方法,它在三個可選參數上使用了這三個調用者信息特性。如果調用方法時顯式指定了這些參數,則會使用真正的參數值。但在下面所示的Main方法中調用時,沒有顯式提供這些值,因此系統將會提供源代碼的文件路徑、調用該方法的代碼行數和調用該方法的成員名稱。

using System;
using System.Runtime.CompilerServices;

public static class Program
{
    public static void MyTrace(string message,
                                [CallerFilePath] string fileName="",
                                [CallerLineNumber] int lineNumber=0,
                                [CallerMemberName] string callingMember="")
    {
        Console.WriteLine("File:         {0}",fileName);
        Console.WriteLine("Line:         {0}",lineNumber);
        Console.WriteLine("Called From:  {0}",callingMember);
        Console.WriteLine("Message:      {0}",message);
    }
    public static void Main()
    {
        MyTrace("Simple message");
    }
}

技術分享

DebuggerStepThrough 特性

我們在單步調試代碼時,常常希望調試器不要進入某些方法。我們只想執行該方法,然後繼續調試下一行。DebuggerStepThrough特性告訴調試器在執行目標代碼時不要進入該方法調試。
在我自己的代碼中,這是最常使用的特性。有些方法很小並且毫無疑問是正確的,在調試時對其反復單步調試只能徒增煩惱。但使用該特性時要十分小心,因為你並不想排除那些可能含有bug的代碼。
關於DebuggerStepThrough要註意以下兩點:

  • 該特性位於System.Diagnostics命名空間
  • 該特性可用於類、結構、構造函數、方法或訪問器

下面這段隨手編造的代碼在一個訪問器和一個方法上使用了該特性。你會發現,調試器調試這段代碼時不會進入IncrementFields方法或X屬性的set訪問器。

using System;
using System.Diagnostics;

class Program
{
    int _x=1;
    int X
    {
        get{return _x;}
        [DebuggerStepThrough]
        set
        {
            _x=_x*2;
            _x+=value;
        }
    }

    public int Y{get;set;}

    public static void Main()
    {
        var p=new Program();
        p.IncrementFields();
        p.X=5;
        Console.WriteLine("X = {0}, Y = {1}",p.X,p.Y);
    }
    [DebuggerStepThrough]
    void IncrementFields()
    {
        X++;
        Y++;
    }
}

其他預定義特性

.NET框架預定義了很多編譯器和CLR能理解和解釋的特性,下表列出了一些。在表中使用了不帶Attribute後綴的短名稱。例如,CLSCompliant的全名是CLSCompliantAttribute。
技術分享

有關應用特性的更多內容


至此,我們演示了特性的簡單使用,都是為方法應用單個特性。這部分內容將會講述其他特性的使用方式。

多個特性

我們可以為單個結構應用多個特性。

  • 多個特性可以使用下面列出的任何一種格式:
    • 獨立的特性片段相互疊在一起
    • 單個特性片段,特性之間使用逗號分隔
  • 我們可以以任何次序列出特性

例如,下面的兩個代碼片段顯示了應用多個特性的兩種方式。兩個片段的代碼是等價的。

[Serializable ]                       //多層結構
[MyAttribute("Simple class", "Version 3.57")]

[MyAttribute("Simple class", "Version 3.57"),Serializable]    //逗號分隔

其他類型的目標

除了類,我們還可以將特性應用到諸如字段和屬性等其他程序結構。以下的聲明顯示了字段上的特性以及方法上的多個特性:

[MyAttribute("Holds a value", "Version 3.2")]    //字段上的特性
public int MyField;

[Obsolete]                                       //方法上的特性
[MyAttribute("Prints out a message.", "Version 3.6")]
public void Printout()
{
    …
}

我們還可以顯式地標註特性,從而將它應用到特殊的目標結構。要使用顯式目標,在特性片段的開始處放置目標類型,後面跟冒號。例如,如下的代碼用特性裝飾方法,並且還把特性應用到返回值上。

顯式目標說明符
   ↓
[method: MyAttribute("Prints out a message.", "Version 3.6")]
[return: MyAttribute("This value represents …", "Version 2.3")]
public long ReturnSetting()
{
    …
}

如下表所列,C#語言定義了10個標準的特性目標。大多數目標名可以自明(self-explanatory),而type覆蓋了類、結構、委托、枚舉和接口。 typevar目標名稱指定使用泛型結構的類型參數。
技術分享

全局特性

我們還可以通過使用assembly和module目標名稱來使用顯式目標說明符把特性設置在程序集或模塊級別。(程序集和模塊在第21章中解釋過。)一些有關程序集級別的特性的要點如下:

  • 程序級級別的特性必須放置在任何命名空間之外,並且通常放置在AssemblyInfo.cs文件中
  • AssemblyInfo.cs文件通常包含有關公司、產品以及版權信息的元數據

如下的代碼行摘自AssemblyInfo.cs文件:

[assembly: AssemblyTitle("SuperWidget")]
[assembly: AssemblyDescription("Implements the SuperWidget product.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("McArthur Widgets, Inc.")]
[assembly: AssemblyProduct("Super Widget Deluxe")]
[assembly: AssemblyCopyright("Copyright ? McArthur Widgets 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture(")]

自定義特性


你或許已經註意到了,應用特性的語法和之前見過的其他語法很不相同。你可能會覺得特性是和結構完全不同的類型,其實不是,特性只是某個特殊類型的類。
有關特性類的一些要點如下。

  • 用戶自定義的特性類叫做自定義特性
  • 所有特性類都派生自System.Attribute

聲明自定義特性

總體來說,聲明一個特性類和聲明其他類一樣。然而,有一些事項值得註意,如下所示。

  • 要聲明一個自定義特性,需要做如下工作
    • 聲明一個派生自System.Attribute的類
    • 給它起一個以後綴Attribute結尾的名字
  • 安全起見,通常建議你聲明一個sealed的特性類(sealed密封類,不能被繼承)

例如,下面的代碼顯示了MyAttributeAttribute特性的聲明的開始部分:

                         特性名                 基類
                           ↓                     ↓
public sealed class MyAttributeAttribute : System.Attribute
{
…
}

由於特性持有目標的信息,所有特性類的公共成員只能是:

  • 字段
  • 屬性
  • 構造函數

使用特性的構造函數

特性和其他類一樣,都有構造函數。每一個特性至少必須有一個公共構造函數。

  • 和其他類一樣,如果你不聲明構造函數,編譯器會為我們產生一個隱式、公共且無參的構造函數
  • 特性的構造函數和其他構造函數一樣,可以被重載
  • 聲明構造函數時必須使用類全名,包括後綴。我們只可以在應用特性時使用短名稱

例如,如果有如下的構造函數(名字沒有包含後綴),編譯器會產生一個錯誤消息:

public MyAttributeAttribute(string desc,string ver)
{
    Description=desc;
    VersionNumber=ver;
}

指定構造函數

當我們為目標應用特性時,其實是在指定應該使用哪個構造函數來創建特性的實例。列在特性應用中的參數其實就是構造函數的參數。
例如,在下面的代碼中,MyAttribute被應用到一個字段和一個方法上。對於字段,聲明指定了使用單個字符串的構造函數。對於方法,聲明指定了使用兩個字符串的構造函數。

[MyAttribute("Holds a value")]    //使用一個字符串的構造函數
public int MyField;

[MyAttribute("version 1.3", "Sal Martin")]    //使用兩個字符串的構造函數
public void MyMethod()
{
    …
}

其他有關特性構造函數的要點如下。

  • 在應用特性時,構造函數的實參必須是在編譯期能確定值的常量表達式
  • 如果應用的特性構造函數沒有參數,可以省略圓括號。例如,如下代碼的兩個類都使用MyAttr特性的無參構造函數。兩種形式的意義是相同的
[MyAttr]
class SomeClass …

[MyAttr()]
class OtherClass …

使用構造函數

和其他類一樣,我們不能顯式調用構造函數。特性的實例創建後,只有特性的消費者訪問特性時才能調用構造函數。這一點與其他類的實例很不相同,這些實例都創建在使用對象創建表達式的位置。應用一個特性是一條聲明語句,它不會決定什麽時候構造特性類的對象。
下圖比較了普通類構造函數的使用和特性的構造函數的使用。

  • 命令語句的實際意義是:"在這裏創建新的類"
  • 聲明語句的意義是:"這個特性和這個目標相關聯,如果需要構造特性,使用這個構造函數"

技術分享

構造函數中的位置參數和命名參數

和普通類的方法與構造方法相似,特性的構造方法同樣可以使用位置參數和命名參數。如下代碼顯示了使用一個位置參數和兩個命名參數來應用一個特性:

                   位置參數            命名參數              命名參數
                      ↓                   ↓                   ↓
[MyAttribute("An excellent class",Reviewer="Amy McArthur",Ver="0.7.15.33")]

下面的代碼演示了特性類的聲明以及為MyClass類應用特性。註意,構造函數的聲明只列出了一個形參,但我們可通過命名參數給構造函數3個實參。兩個命名參數設置了字段Ver和Reviewer的值。

public sealed class MyAttributeAttribute : System.Attribute
{
    public string Description;
    public string Ver;
    public string Reviewer;

    public MyAttributeAttribute(string desc) //一個形參
    {
        Description = desc;
    }
}
             //三個實參
[MyAttribute("An excellent class”, Reviewer="Amy McArthur", Ver="7.15.33")]
class MyClass
{
    …
}

構造函教需要的任何位置參數都必須放在命名參數之前。

限制特性的使用

我們已經看到了可以為類應用特性。而特性本身就是類,有一個很重要的預定義特性可以用來應用到自定義特性上,那就是AttributeUsage特性。我們可以使用它來限制特性使用在某個目標類型上。
例如,如果我們希望自定義特性MyAttribute只能應用到方法上,那麽可以以如下形式使用AttributeUsage:

                   只針對方法
                       ↓
[AttributeUsage( AttributeTarget.Method )]
public sealed class MyAttributeAttribute : System.Attribute
{
    …
}

AttributeUsage有三個重要的公共屬性,如下表所示。表中顯示了屬性名和屬性的含義。對於後兩個屬性,還顯示了它們的默認值。
技術分享
AttributeUsage的構造函數
AttributeUsage的構造函數接受單個位置參數,該參數指定了特性允許的目標類型。它用這個參數來設置ValidOn屬件,可接受目標類型是AttributeTarget枚舉的成員。AttributeTarget枚舉的完整成員列表如下表所示。
我們可以通過使用按位或運算符來組合使用類型。例如,在下面的代碼中,被裝飾的特性只能應用到方法和構造函數上。

                                     目標
                                       ↓
[AttributeUsage( AttributeTarget.Method| AttributeTarget.Constructor )]
public sealed class MyAttributeAttribute : System.Attribute

技術分享
當我們為特性聲明應用AttributeUsage時,構造函數至少需要一個參數,參數包含的目標類型會保存在ValidOn中。我們還可以通過使用命名參數有選擇性地設置Inherited和AllowMultiple屬性。如果我們不設置,它們會保持如表24-4所示的默認值。
作為示例,下面一段代碼指定了MyAttribute的如下方面。

  • MyAttribute能且只能應用到類上
  • MyAttribute不會被應用它的派生類所繼承
  • 不能有MyAttribute的多個實例應用到同一個目標上
[AttributeUsage( AttributeTarget.Class,     //必需的,位置參數
                Inherited = false,          //可選的,命名參數
                AllowMultiple = false )]    //可選的,命名參數
public sealed class MyAttributeAttribute : System.Attribute
{
    …
}

自定義特性的最佳實踐

強烈推薦編寫自定義特性時參考如下實踐。

  • 特性類應該表示目標結構的一些狀態
  • 如果特性需要某些字段,可以通過包含具有位置參數的構造函數來收集數據,可選字段可以采用命名參數按需初始化
  • 除了屬性之外,不要實現公共方法或其他函數成員
  • 為了更安全,把特性類聲明為sealed
  • 在特性聲明中使用AttributeUsage來顯式指定特性目標組

如下代碼演示了這些準則:

[AttributeUsage( AttributeTargets.Class )]
public sealed class ReviewCommentAttribute : System.Attribute
{
    public    string    Description     {get;set;}
    public    string    VersionNumber   {get;set;}
    public    string    ReviewerID      {get;set;}

    public ReviewCommentAttribute(string desc, string ver)
    {
        Description = desc;
        VersionNumber = ver;
    }
}

訪問特性


在本章開始處,我們已經看到了可以使用Type對象來獲取類型信息。對於訪問自定義特性來說,我們也可以這麽做。Type的兩個方法(IsDefined和GetCustomAttributes)在這裏非常有用。

使用IsDefined方法

我們可以使用Type對象的IsDefined方法來檢測某個特性是否應用到了某個類上。
例如,以下的代碼聲明了一個有特性的類MyClass,並且作為自己特性的消費者在程序中訪問聲明和被應用的特性。代碼的開始處是MyAttribute特性和應用特性的MyClass類的聲明。這段代碼做了下面的事情。

  • 首先,Main創建了類的一個對象。然後通過使用從object基類繼承的GetType方法獲取了Type對象的一個引用
  • 有了Type對象的引用,就可以調用IsDefined方法來判斷ReviewComment特性是否應用到了這個類
    • 第一個參數接受需要檢査的特性的Type對象
    • 第二個參數是bool類型的,它指示是否搜索MyClass的繼承樹來查找這個特性
[AttributeUsage(AttributeTargets.Class)]
public sealed class ReviewCommentAttribute:System.Attribute
{…}

[ReviewComment("Check it out","2.4")]
class MyClass{}

class Program
{
    static void Main()
    {
        var mc=new MyClass();
        Type t=mc.GetType();
        bool isDefined=
            t.IsDefined(typeof(ReviewCommentAttribute),false);
        if(isDefined)
            Console.WriteLine("ReviewComment is applied to type {0}",t.Name);
    }
}

技術分享

使用GetCustomAttributes方法

GetCustomAttributes方法返回應用到結構的特性的數組。

  • 實際返冋的對象是object的數組,因此我們必須將它強制轉換為相應的特性類型
  • 布爾參數指定是否搜索繼承樹來査找特性
  • object[] AttArr = t.GetCustomAttributes(false);
  • 調用GetCustomAttributes方法後,每一個與目標相關聯的特性的實例就會被創建

下面的代碼使用了前面的示例中相同的特性和類聲明。但是,在這種情況下,它不檢測特性是否應用到了類,而是獲取應用到類的特性的數組,然後遍歷它們,輸出它們的成員的值。

using System;

[AttributeUsage(AttributeTargets.Class)]
public sealed class MyAttributeAttribute:System.Attribute
{
    public  string  Description  {get;set;}
    public  string  VersionNumber{get;set;}
    public  string  ReviewerID   {get;set;}

    public MyAttributeAttribute(string desc,string ver)
    {
        Description=desc;
        VersionNumber=ver;
    }
}

[MyAttribute("Check it out","2.4")]
class MyClass
{
}
class Program
{
    static void Main()
    {
        Type t=typeof(MyClass);
        object[] AttArr=t.GetCustomAttributes(false);

        foreach(Attribute a in AttArr)
        {
            var attr=a as MyAttributeAttribute;
            if(null!=attr)
            {
                Console.WriteLine("Description    :{0}",attr.Description);
                Console.WriteLine("Version Number :{0}",attr.VersionNumber);
                Console.WriteLine("Reviewer ID    :{0}",attr.ReviewerID);
            }
        }
    }
}

技術分享

C#圖解教程 第二十四章 反射和特性