在C#中,許多地方都用到了特徵屬性Attribute,它可以用來描述類型或方法,從而賦予它們一些額外的功能或特性。事實上,當你開啟Visual Studio並建立一個Windows Forms Application應用程式時,在預設的Program.cs檔案中,Main函數就被標記上了特徵屬性。
[STAThread]
static void Main()
{
}
特徵屬性是一群組繼承自Attribute的類,通常被用來修飾類型或方法,在類型或方法定義之前以方括弧括起來。如我們可以給類的屬性添加特徵屬性使其可以被序列化為XML:
public class Movie
{
[XmlElement("MovieName")]
public string Title
{ get; set; }
[XmlElement("MovieRating")]
public float Rating
{ get; set; }
[XmlElement("MovieReleaseDate")]
public DateTime ReleaseDate
{ get; set; }
}
如同上面的程式碼片段,特徵屬性允許接收參數,這要取決於你使用了什麼樣的特徵屬性類型。當然,在.NET中我們也可以自訂特徵屬性類,但前提是自訂類必須繼承自Attribute類。下面是一個例子:
class MyToken : Attribute
{
public string DisplayName { get; set; }
public MyToken(string dn)
{ DisplayName = dn; }
}
然後,我們在代碼中使用上面我們自訂的特徵屬性類:
public static class Operations
{
[MyToken("Operation 1")]
public static void Operation1()
{
System.Windows.Forms.MessageBox.Show("Hey, I'm operation 1!");
}
[MyToken("Operation 2")]
public static void Operation2()
{
System.Windows.Forms.MessageBox.Show("Hey, I'm operation 2!");
}
}
那麼我們在方法前使用我們自訂的特徵屬性有什麼意義呢?來看看一個實際的例子,我們通過反射來找出所有被標記成我們自訂的特徵屬性的方法。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
PopulateComBox();
}
private void PopulateComBox()
{
MethodInfo[] methods = typeof(Operations).GetMethods();
MyToken token = null;
List<KeyValuePair<String, MethodInfo>> items = new List<KeyValuePair<string, MethodInfo>>();
foreach (MethodInfo method in methods)
{
token = Attribute.GetCustomAttribute(method, typeof(MyToken), false) as MyToken;
if (token == null)
{
continue;
}
items.Add(new KeyValuePair<String, MethodInfo>(token.DisplayName, method));
}
this.comboBox1.DataSource = items;
this.comboBox1.DisplayMember = "Key";
this.comboBox1.ValueMember = "Value";
}
private void button1_Click(object sender, EventArgs e)
{
if (this.comboBox1.SelectedIndex < 0)
{
return;
}
MethodInfo method = this.comboBox1.SelectedValue as MethodInfo;
if (method != null)
{
method.Invoke(null, null);
}
}
}
public class MyToken : Attribute
{
public string DisplayName { get; set; }
public MyToken(string dn)
{ DisplayName = dn; }
}
public static class Operations
{
[Obsolete]
public static void Operation0()
{
System.Windows.Forms.MessageBox.Show("Hey, I'm operation 0!");
}
[MyToken("Operation 1")]
public static void Operation1()
{
System.Windows.Forms.MessageBox.Show("Hey, I'm operation 1!");
}
[MyToken("Operation 2")]
public static void Operation2()
{
System.Windows.Forms.MessageBox.Show("Hey, I'm operation 2!");
}
public static void Operation3()
{
System.Windows.Forms.MessageBox.Show("Hey, I'm operation 3!");
}
}
}
上面是一個WinForm程式的完整代碼,表單上一共兩個控制項,一個Combox,一個Button。程式啟動時調用PopulateComBox()方法,通過反射找出Operations類中所有被標記為MyToken特徵屬性的方法,添加到集合List中。這裡有兩個地方需要說明一下:
1. 通過Attribute.GetCustomAttribute()方法嘗試找到MethodInfo[]數組中所有被標記為MyToken特徵屬性的方法。注意GetCustomAttribute()方法的參數形式。typeof(Operations).GetMethods()用來傳回型別Operations中所有public存取層級的方法。
2. 集合items被定義為List泛型型別,該List泛型型別被指定為具有名值對的形式。實際上你也可以使用任何具有名值對的集合類型,如Hashtable。
當點擊按鈕的時候,程式通過ComBox.SelectedValue屬性找到當前選中的方法名稱,然後使用Invoke()方法執行它,下面是程式執行時的效果。
你應該注意到了在ComBox中只有兩項:Operation1和Operation2。但是我們看到Operations類中一共有四個被定義為public存取層級的static方法,為什麼反射只找到了兩個方法呢?這是因為我們在反射中規定只找出被定義為MyToken特徵屬性的方法,方法Operation0被定義為特徵屬性Obsolete(該特徵屬性用於標識方法已到期並不再使用,你應該在一些早期.NET版本中發布的類型或方法中見過這個標識,或者在代碼的智能提示中見過它)而非MyToken,方法Operation3不包含有特徵屬性。 反射的最大好處就是可以動態地載入程式集,並擷取程式集中的類型,以及類型中的屬性和方法,並執行它們。在上例中,如果你想添加一個新的Operation方法並讓它在ComBox中出現且能被執行,你只需要做一件事情,那就是在Operations類中增加一個方法並標註為MyToken特徵屬性,同時加上一些你期待執行的代碼。