C#特性詳解


    特性提供功能強大的方法,用以將元數據或聲明信息與代碼(程序集、類型、方法、屬性等)相關聯。特性與程序實體關聯后,即可在運行時使用名為“反射”的技術查詢特性。這篇文章絕大部分是按照MSDN來學習的,只是加了一點點自己的東東,官方介紹的很詳細,我們就一起來了解一下它的用法。

特性具有以下屬性

  • 特性可向程序中添加元數據。元數據是有關在程序中定義的類型的信息。所有的 .NET 程序集都包含指定的一組元數據,這些元數據描述在程序集中定義的類型和類型成員。可以添加自定義特性,以指定所需的任何附加信息。

  • 可以將一個或多個特性應用到整個程序集、模塊或較小的程序元素(如類和屬性)。

  • 特性可以與方法和屬性相同的方式接受參數。

  • 程序可以使用反射檢查自己的元數據或其他程序內的元數據。

這些都是官方的定義,那么對於一個初學者來說,看的懂漢字不難,但是上面的元數據是什么?

我這么通俗的解釋下:

  你注意過程序及編譯的時候的pdb文件了嗎?pdb文件里面存儲了,關於程序集內部的所有的成員信息,例如,成員的數據類型,屬性類型,方法返回值,方法入參類型,就是程序及內部所有的定義信息的數據信息,是存儲定義信息的一類數據信息,程序集里面的所有的關於聲明類的數據信息,包括方法間調用,都是存儲在元數據里面。

下面開始一同學習特性的用法:

特性可以放置在幾乎所有的聲明中。在 C# 中,特性的指定方法為:將括在方括號中的特性名置於其應用到的實體的聲明上方

[System.Serializable] public class SampleClass { // Objects of this type can be serialized.
}

 

一個聲明上可放置多個特性:

 

using System.Runtime.InteropServices; ... void MethodA([In][Out] ref double x) { } void MethodB([Out][In] ref double x) { } void MethodC([In, Out] ref double x) { }

 

某些特性對於給定實體可以指定多次。例如,ConditionalAttribute 就是一個可多次使用的特性:

[Conditional("DEBUG"), Conditional("TEST1")] void TraceMethod() { // ...
}

 

根據約定,所有特性名稱都以單詞“Attribute”結束,以便將它們與“.NET Framework”中的其他項區分。但是,在代碼中使用特性時,不需要指定 attribute 后綴例如,[DllImport] 雖等效於 [DllImportAttribute],但 DllImportAttribute 才是該特性在 .NET Framework 中的實際名稱

特性參數

許多特性都有參數,而這些參數可以是定位參數、未命名參數或命名參數任何定位參數都必須按特定順序指定並且不能省略,而命名參數是可選的且可以按任意順序指定首先指定定位參數例如,這三個特性是等效的:

[DllImport("user32.dll")] [DllImport("user32.dll", SetLastError=false, ExactSpelling=false)] [DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

 

第一個參數(DLL 名稱)是定位參數並且總是第一個出現,其他參數為命名參數在這種情況下,兩個命名參數均默認為 false,因此可將其省略。有關默認參數值的信息,可以參考參考各個特性的文檔。

特性目標

特性的目標是應用該特性的實體例如,特性可以應用於類、特定方法或整個程序集。默認情況下,特性應用於它后面的元素。但是,您也可以顯式標識將特性應用於方法還是它的參數或返回值

若要顯式標識特性目標,請使用下面的語法:

[target : attribute-list]

 

下表顯示了可能的 target的列表。

C#

適用對象

assembly

整個程序集

module

當前程序集模塊(不同於 Visual Basic 模塊)

field

在類或結構中的字段

event

Event

method

方法或 getset 屬性訪問器

param

方法參數或 set 屬性訪問器參數

property

Property

return

方法、屬性索引器或 get 屬性訪問器的返回值

type

結構、類、接口、枚舉或委托

 

下面的示例演示如何將特性應用於程序集和模塊。 

using System; using System.Reflection; [assembly: AssemblyTitleAttribute("Production assembly 4")] [module: CLSCompliant(true)]

下面的示例演示如何在 C# 中將特性應用於方法、方法參數和方法返回值。

// default: applies to method
[SomeAttr] int Method1() { return 0; } // applies to method
[method: SomeAttr] int Method2() { return 0; } // applies to return value
[return: SomeAttr] int Method3() { return 0; }

 

無論規定 SomeAttr 應用於什么目標,都必須指定 return 目標,即使 SomeAttr 被定義為僅應用於返回值也是如此。換言之,編譯器將不使用 AttributeUsage 信息解析不明確的特性目標。

呀,終於了解完了,是時候自己十一下手了,我們就動手活動一下頸骨,多加點注釋來關聯一下學過的內容。

我們這里拿ObsoleteAttribute做下測試,它標記不再使用的程序元素。此類不能被繼承。首先我們看一下它的繼承結構。

當然我們看看其他的特性,我們就會發現特性其實是從System.Object類派生出來的一種特殊類。

 我們現在用這個構造來驗證

public ObsoleteAttribute(string message, bool error)

 

  參數                         類型:

  message                   System ..::.String        描述可選的變通方法的文本字符串。

 error                    System ..::.Boolean       指示是否將使用已過時的元素視為錯誤的布爾值。


總之我們在使用特性的時候不要產生畏懼,就當他是特殊的類,以前怎么樣用構造函數現在仍舊怎么用只是格式有點微妙的變化。

using System; namespace 特性 { class Program { static void Main(string[] args) { OldClass old = new OldClass();//2個報錯,因為使用OldClass兩次
old.OldMethod();//警告。因為第二個參數未指定使用已過時的元素不會視為錯誤 Console.ReadKey(); } } [Obsolete("該類已經過時",true)]//使用默認的特性目標,直接作用於緊隨其后的Class OldClass //第二個參數我這里設置為true將使用已過時的元素視為錯誤 class OldClass { [method: Obsolete("該方法已經過時")] public void OldMethod() { Console.WriteLine("過時的方法!"); } } }

運行以后會出現兩個錯誤提示,一個警告提示:

好了,現在我們在緊接着學習自定義特性,這個估計就算是相當簡單了。
自定義特性

通過定義一個特性類,可以創建您自己的自定義特性。該特性類直接或間接地從Attribute派生,有助於方便快捷地在元數據中標識特性定義。假設您要用編寫類型的程序員的名字標記類型。可以定義一個自定義 Author特性類:

[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct) ] public class Author : System.Attribute { private string name; public double version; public Author(string name) { this.name = name; version = 1.0; } }

 

類名是特性的名稱,即 Author它由 System.Attribute 派生而來,因此是自定義特性類。構造函數的參數是自定義特性的定位參數。本示例中 name 是定位參數。任何公共的讀寫字段或屬性都是命名參數。在本例中,version 是唯一的命名參數。請注意 AttributeUsage 特性的用法,它使得 Author 特性僅在類聲明中有效。

可以按如下所示使用此新特性:

[Author("P. Ackerman", version = 1.1)] class SampleClass { // P. Ackerman's code goes here...
}

AttributeUsage 有一個命名參數 AllowMultiple,使用它可以使自定義特性成為一次性使用或可以使用多次的特性。在下面的代碼示例中,創建了一個使用多次的特性。

[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple = true)  // multiuse attribute
] public class Author : System.Attribute

 

在下面的代碼示例中,向某個類應用了同一類型的多個特性。

 

[Author("P. Ackerman", version = 1.1)] [Author("R. Koch", version = 1.2)] class SampleClass { // P. Ackerman's code goes here... // R. Koch's code goes here...
}

 如果特性類包含一個屬性,則該屬性必須為讀寫屬性。


 介紹完了官方的示例是不是還是雲里霧里,那么我們一起來深入解剖一下。

首先我們從上面可以總結出創建自定義特性的大概步驟:

1.應用AttributeUsage特性 雖然等效,但AttributeUsageAttribute 才是該特性在 .NET Framework 中的實際名稱,它也是由 System.Attribute 派生而來。

2.聲明特性類,它由 System.Attribute 派生而來。

3.聲明構造函數 

4.聲明特性

OVER!!!就這么回事,完了嗎,我們繼續剖析之重要的信息,AttributeUsage特性。

AttributeUsage特性,研究特性當然首要的要研究其構造函數。現在我們來看看他是怎么定義的。

public AttributeUsageAttribute( AttributeTargets validOn)

 參數   validOn 類型:System.AttributeTargets 使用按位“或”運算符組合的一組值,用於指示哪些程序元素是有效的。

用指定的 AttributeTargets、AllowMultiple 值和 Inherited 值列表初始化 AttributeUsageAttribute 類的新實例。

於是乎我們返回到了研究AttributeTargets的問題了。現在我們就來細心的看看他是神馬!
原來他是一個枚舉,通過該特性可使其成員值按位組合。可以通過按位“或”運算組合 AttributeTargets 枚舉值來獲得首選組合

成員


 

  成員名稱 說明
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Assembly 可以對程序集應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Module 可以對模塊應用特性。
注意注意
Module 指的是可移植的可執行文件(.dll 或 .exe),而非 Visual Basic 標准模塊。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Class 可以對類應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Struct 可以對結構應用特性,即值類型。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Enum 可以對枚舉應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Constructor 可以對構造函數應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Method 可以對方法應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Property 可以對屬性應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Field 可以對字段應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Event 可以對事件應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Interface 可以對接口應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Parameter 可以對參數應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif Delegate 可以對委托應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif ReturnValue 可以對返回值應用特性。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif GenericParameter 可以對泛型參數應用特性。
注意注意
目前,此特性可以應用僅於 C#、Microsoft 中間語言 (MSIL) 和發出的代碼。
由 XNA Framework 提供支持fa1240fs.PortableClassLibrary(zh-cn,VS.100).gif All 可以對任何應用程序元素應用特性。

 


 到了這里一節也就明了了,謎底都一一展現在我們的面前。

 按照上面的經驗,再次開始動手來熟悉這一切,我指定了該自定義的特性不可繼承,就在不解釋別的了只是為了證明一下命名參數Inherited定性成功與否,總之還是很簡單的。

using System;

namespace 特性
{
    class Program
    {
        static void Main(string[] args)
        {
            GetAttributeInfo(typeof(OldClass));
            Console.WriteLine("==============");
            GetAttributeInfo(typeof(NewClass));
            Console.ReadKey();
        }
        public static void GetAttributeInfo(Type t)
        {
            OldAttribute myattribute = (OldAttribute)Attribute.GetCustomAttribute(t, typeof(OldAttribute));
            if (myattribute == null)
            {
                Console.WriteLine(t.ToString()+"類中自定義特性不存在!");
            }
            else
            {
                Console.WriteLine("特性描述:{0}\n加入事件{1}", myattribute.Discretion, myattribute.date);
            }
        }
    }

   [AttributeUsage(AttributeTargets.Class,Inherited=false)]//設置了定位參數和命名參數

        //該特性適用於所有的類,而且是非繼承的。
    class OldAttribute : Attribute//繼承自Attribute
    {
        private string discretion;

        public string Discretion
        {
            get { return discretion; }
            set { discretion = value; }
        }
        public DateTime date;
        public OldAttribute(string discretion)
        {
            this.discretion = discretion;
            date = DateTime.Now;
        }
    }
    //現在我們定義兩類
    [Old("這個類將過期")]//使用定義的新特性
    class OldClass
    {
        public void OldTest()
        {
            Console.WriteLine("測試特性");
        }
    }
    class NewClass:OldClass
    {
        public void NewTest()
        {
            Console.WriteLine("測試特性的繼承");
        }
    }
    //我們寫一個方法用來獲取特性信息
}

 


運行效果:


今天就到此了,睡覺覺了!希望同學們能略有所獲。

 

 


免責聲明!

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



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