C# 知識回顧 - 特性 Attribute
【部落客】反骨仔 【原文位址】http://www.cnblogs.com/liqingwen/p/5911289.html
目錄
- 特性簡介
- 使用特性
- 特性的參數
- 特性的目标
- 特性的常見用途
- 建立自定義的特性
- 使用反射通路特性
一、特性簡介
特性提供功能強大的方法,用以将中繼資料或聲明資訊與代碼(程式集、類型、方法、屬性等)相關聯。特性與程式實體關聯後,可在運作時使用“反射”查詢特性。
特性具有以下屬性:
(1)特性可向程式中添加中繼資料。中繼資料是有關在程式中定義的類型的資訊。所有的 .NET 程式集都包含指定的一組中繼資料,這些中繼資料描述在程式集中定義的類型和類型成員。可以添加自定義特性,以指定所需的任何附加資訊。
(2)可以将一個或多個特性應用到整個程式集、子產品或較小的程式元素(如類和屬性)。
(3)特性可以與方法和屬性相同的方式接受參數。
(4)程式可以使用反射檢查自己的中繼資料或其他程式内的中繼資料。
二、使用特性
特性可以放置在幾乎所有的聲明中(但特定的特性可能限制在其上有效的聲明類型)。在 C# 中,特性的指定方法為:将括在方括号中的特性名置于其應用到的實體的聲明上方。它必須位于所應用于的元素的緊前面并與該元素在同一行。
1 [Serializable] //使用特性 SerializableAttribute
2 internal class MyClass
3 {
4 [DllImport("user32.dll")] //使用特性 DllImportAttribute
5 private static extern void Do();
6
7 #region 一個聲明上可放置多個特性
8
9 private void MethodA([In][Out]ref double n) { }
10 private void MethodB([In, Out]ref double n) { }
11
12 #endregion 一個聲明上可放置多個特性
13
14 #region 某些特性對于給定實體可以指定多次
15
16 [Conditional("DEBUG"), Conditional("TEST1")]
17 private void TraceMethod() { }
18
19 #endregion 某些特性對于給定實體可以指定多次
20 }
【注意】根據約定,所有特性名稱都以單詞“Attribute”結束,以便将它們與“.NET Framework”中的其他項區分。但是,在代碼中使用特性時,不需要指定 attribute 字尾。
三、特性的參數
許多特性都有參數,而這些參數可以是定位參數、未命名參數或命名參數。任何定位參數都必須按特定順序指定并且不能省略,而命名參數是可選的且可以按任意順序指定。首先指定定位參數。例如,這三個特性是等效的:
1 [DllImport("user32.dll")]
2 [DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
第一個參數(DLL 名稱)是定位參數并且總是第一個出現,其他參數為命名參數。在這種情況下,兩個命名參數均預設為 false,是以可将其省略。
四、特性的目标
特性的目标是應用該特性的實體。例如,特性可以應用于類、特定方法或整個程式集。預設情況下,特性應用于它後面的元素。但是,您也可以顯式辨別要将特性應用于方法還是它的參數或傳回值。
若要顯式辨別特性目标,文法:
[target : attribute-list]
C# | 适用對象 |
assembly | 整個程式集 |
module | 目前程式集子產品 |
field | 在類或結構中的字段 |
event | |
method | 方法或 get 和 set 屬性通路器 |
param | 方法參數或 set 屬性通路器參數 |
property | 屬性 |
return | 方法、屬性索引器或 get 屬性通路器的傳回值 |
type | 結構、類、接口、枚舉或委托 |
//示例:将特性應用于程式集和子產品
[assembly: AssemblyTitle("assembly 4.6.1")]
[module: CLSCompliant(true)]
1 //示例:将特性應用于方法、方法參數和方法傳回值
2
3 //預設:應用于方法
4 [SomeAttr]
5 int Method1() { return 0; }
6
7 //指定應用于方法
8 [method: SomeAttr]
9 int Method2() { return 0; }
10
11 //指定應用于傳回值
12 [return: SomeAttr]
13 int Method3() { return 0; }
五、特性的常見用途
以下清單包含特性的幾個常見用途:
(1)在 Web 服務中,使用 WebMethod 特性來标記方法,以訓示該方法應該可通過 SOAP 協定進行調用。
(2)描述當與本機代碼進行互動操作時如何封送方法參數。有關更多資訊。
(3)描述類、方法和接口的 COM 屬性。
(4)使用 DllImportAttribute 類調用非托管代碼。
(5)在标題、版本、說明或商标方面描述您的程式集。
(6)描述要持久性序列化類的哪些成員。
(7)描述如何映射類成員和 XML 節點以便進行 XML 序列化。
(8)描述方法的安全要求。
(9)指定用于強制安全性的特性。
(10)由實時 (JIT) 編譯器控制優化,以便易于調試代碼。
(11)擷取有關調用方的資訊的方法。
六、建立自定義的特性
通過定義一個特性類,可以建立您自己的自定義特性。該特性類直接或間接地從 Attribute 派生,有助于友善快捷地在中繼資料中辨別特性定義。
1 /// <summary>
2 /// 角色特性
3 /// </summary>
4 /// RoleAttribute:特性的名稱,繼承 Attribute,為自定義特性
5 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
6 public class RoleAttribute : Attribute
7 {
8 private string _name;
9
10 /// <summary>
11 /// 啟用辨別
12 /// </summary>
13 /// IsEnable:命名參數
14 public bool IsEnable { get; set; }
15
16 /// <summary>
17 /// 構造函數
18 /// </summary>
19 /// <param name="name"></param>
20 /// name:定位參數
21 public RoleAttribute(string name)
22 {
23 _name = name;
24 }
25 }
1 [Role("Me", IsEnable = true)] //調用特性的方式
2 public class OurClass
3 {
4
5 }
構造函數的參數是自定義特性的定位參數,任何公共的讀寫字段或屬性都是命名參數。【注意】 AttributeUsage 特性,在這裡它使得 Role 特性僅在類和 struct 聲明中有效。
1 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)] //AllowMultiple:該值訓示能否為一個程式多次使用該特性
2 public class RoleAttribute : Attribute
3 {
4 //... ...
5 }
1 [Role("You")] //在同一個類上多次使用
2 [Role("Me", IsEnable = true)]
3 public class OurClass
4 {
5 //... ...
6 }
【注意】如果特性類包含一個屬性,則該屬性必須為讀寫屬性。
七、使用反射通路特性
使用反射,可檢索用自定義特性定義的資訊。主要方法是 GetCustomAttributes,它傳回對象數組,這些對象在運作時等效于源代碼特性。

1 /// <summary>
2 /// 角色特性
3 /// </summary>
4 /// RoleAttribute:特性的名稱,繼承 Attribute,為自定義特性
5 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
6 public class RoleAttribute : Attribute
7 {
8 private string _name;
9 /// <summary>
10 /// 啟用辨別
11 /// </summary>
12 public bool IsEnable { get; set; }
13
14 /// <summary>
15 /// 構造函數
16 /// </summary>
17 /// <param name="name"></param>
18 public RoleAttribute(string name)
19 {
20 _name = name;
21 }
22 }
RoleAttribute.cs
1 [Role("Me", IsEnable = true)]
2 public class OurClass
3 {
4 //... ...
5 }
概念上等效于
1 RoleAttribute role = new RoleAttribute("Me");
2 role.IsEnable = true;
但是,直到查詢 OurClass 來擷取特性後才會執行此代碼。對 OurClass 調用 GetCustomAttributes 會導緻按上述方式構造并初始化一個 RoleAttribute 對象。如果該類具有其他特性,則按相似的方式構造其他特性對象。然後 GetCustomAttributes 傳回 RoleAttribute 對象和數組中的任何其他特性對象。之後就可以對此數組進行疊代,确定根據每個數組元素的類型所應用的特性,并從特性對象中提取資訊。
這裡,定義一個自定義特性,将其應用于若幹實體并通過反射進行檢索。
1 /// <summary>
2 /// 角色特性
3 /// </summary>
4 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
5 public class RoleAttribute : Attribute
6 {
7 private readonly string _name;
8
9 /// <summary>
10 /// 啟用辨別
11 /// </summary>
12 public bool IsEnable { get; set; }
13
14 /// <summary>
15 /// 構造函數
16 /// </summary>
17 /// <param name="name"></param>
18 public RoleAttribute(string name)
19 {
20 _name = name;
21 }
22
23 public string GetName()
24 {
25 return _name;
26 }
27 }
class MyClass1 { }
[Role("Me")]
class MyClass2 { }
[Role("Me"), Role("You", IsEnable = true)]
class MyClass3 { }
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Output(typeof(MyClass1));
6 Output(typeof(MyClass2));
7 Output(typeof(MyClass3));
8
9 Console.Read();
10 }
11
12 /// <summary>
13 /// 輸出
14 /// </summary>
15 /// <param name="t"></param>
16 static void Output(Type t)
17 {
18 Console.WriteLine($"Class: {t}");
19
20 var attributes = t.GetCustomAttributes();
21 foreach (var attribute in attributes)
22 {
23 var attr = attribute as RoleAttribute;
24
25 if (attr == null)
26 {
27 return;
28 }
29
30 Console.WriteLine($" Name: {attr.GetName()}, IsEnable: {attr.IsEnable}");
31 }
32 }
33 }
傳送門
《隻是想簡單說下表達式樹 - Expression Trees》
《隻是想簡單說下序列化》
【參考】微軟官方文檔