特性提供功能強大的方法,用以將元數據或聲明信息與代碼(程序集、類型、方法、屬性等)相關聯。特性與程序實體關聯后,即可在運行時使用名為“反射”的技術查詢特性。這篇文章絕大部分是按照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 |
方法或 get 和 set 屬性訪問器 |
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 枚舉值來獲得首選組合。
成員
成員名稱 | 說明 | |
---|---|---|
![]() ![]() |
Assembly | 可以對程序集應用特性。 |
![]() ![]() |
Module | 可以對模塊應用特性。
![]()
Module 指的是可移植的可執行文件(.dll 或 .exe),而非 Visual Basic 標准模塊。
|
![]() ![]() |
Class | 可以對類應用特性。 |
![]() ![]() |
Struct | 可以對結構應用特性,即值類型。 |
![]() ![]() |
Enum | 可以對枚舉應用特性。 |
![]() ![]() |
Constructor | 可以對構造函數應用特性。 |
![]() ![]() |
Method | 可以對方法應用特性。 |
![]() ![]() |
Property | 可以對屬性應用特性。 |
![]() ![]() |
Field | 可以對字段應用特性。 |
![]() ![]() |
Event | 可以對事件應用特性。 |
![]() ![]() |
Interface | 可以對接口應用特性。 |
![]() ![]() |
Parameter | 可以對參數應用特性。 |
![]() ![]() |
Delegate | 可以對委托應用特性。 |
![]() ![]() |
ReturnValue | 可以對返回值應用特性。 |
![]() ![]() |
GenericParameter | 可以對泛型參數應用特性。
![]()
目前,此特性可以應用僅於 C#、Microsoft 中間語言 (MSIL) 和發出的代碼。
|
![]() ![]() |
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("測試特性的繼承");
}
}
//我們寫一個方法用來獲取特性信息
}
運行效果:
今天就到此了,睡覺覺了!希望同學們能略有所獲。