在.NET架構中,經常用到屬性,從定義哪些類是可序列化到選擇某個Web服務應用中的哪些方法是可以公開的都會用到屬性。使用屬性可以在設計時對類、properties和方法添加說明,然後在運行時通過反射資訊來檢查它們。本文為我們介紹了在開發應用時如何利用C#自訂屬性。
本文可以從技術文章下載出獲得,其中包含了一個使用定製屬性的Visual Studio項目樣本檔案。
屬性類是設計時可應用於類、properties和方法的特殊文類。屬性類提供描述元素某些方面屬性的方式或決定依附於該元素的其它類的行為,進而在運行時可以訪問和檢驗這些描述與行為。你可以將屬性類看作為類成員添加特殊修改器的一種方式。
例如,如果你曾經寫過Web服務,那肯定知道要使得方法在整個服務中是公開的,必須要使用WebMethod屬性。這是一個示範屬性應用的很好的例子,因為我們要用WebMethod屬性擴充編程模型。C#中沒有內建的方式來指定某個方法通過Web服務是可見的(因為內建有表明一個方法是私人的方式),因此需要添加WebMethod屬性來滿足這一需要。
設計自訂屬性
設計自訂屬性的過程十分簡單,在設計屬性前只需要考慮以下幾個方面:
屬性可以以很多方式使用。你需要定義屬性到底要完成什麼功能並確保這些特定功能沒有內建在.NET框架組中。使用.NET修改器要比使用屬性好,因為這將簡化同其它裝配件的整合過程。
屬性是打算用來指示某個功能的簡單標誌嗎?或者屬性是否要儲存資訊?一個屬性可以擁有設計時賦予的一組資訊,並在運行時查看這些資訊。例如,看一下樣本應用中的別名屬性。
大多數情況下,可以將屬性包含在使用該屬性的裝配件中。不過也有這樣的例子,將屬性駐留在公用的、輕量級的、共用裝配件中會更好些。這種類型的配置允許客戶使用屬性時不必引用不需要的裝配件。
如果沒有模組讀取屬性,那麼它將一文不值。你很可能將讀取屬性的類放在屬性駐留的同一個裝配件中。然而,正像前面提到的,也有這樣的例子,你想將讀取屬性的方法與屬性自身分別放在不同的裝配件中。
使用屬性
在我們深入瞭解如何設計自訂屬性之前,我們需要先看一下它們是如何使用的。例如,假
定我們有一個稱為“Hide”的屬性它能夠有效地隱藏Properties,因此它們不會顯示在螢幕上。如果我們將這個屬性應用於“SSN”property,那麼代碼將會如列表A所示。
列表 A
[Hide ()]
public string SSN
{
get { return _ssn; }
set { _ssn = value; }
}
作為更複雜一點的例子,假設我們將有一個屬性稱為“Alias”。該屬性的任務是檢查一個property可能擁有別名。這將允許將一個property值對應給另一個property即使批roperty的名字不匹配。這個屬性接受一系列字串值作為映射名。(列表B)
列表 B
[Alias ("FirstName", "First")]
public string FName
{
get { return _fName; }
set { _fName = value; }
}
在這個例子中,property“FName”被映射到“FirstName”和“First”,請查看樣本應用以更詳細的瞭解這種應用。
建立屬性
建立屬性是一個簡單的過程。你可以定義繼承自System.Attribute類的一個包含你想要儲存的資料的類。列表C的前半部分顯示了如何建立一個名為“Alias”的屬性。
列表 C
Class Alias : System.Attribute
{
string[] _names;
public Alias(params string[] names)
{
this.Names = names;
}
public string[] Names
{
get { return _names; }
set { _names = value; }
}
}
正如你所看到的,這就是一個普通的類,唯一的例外就是繼承自System.Attribute類。我們不需要作任何特別的事情使它成為一個類。我們只是簡單的定義了一個需要使用的建構函式並建立了一個property和一個儲存資料的私人成員。
列表D是個更簡單的屬性——“Hide”屬性。這個屬性不需要建構函式(使用預設的構建函數),也不儲存資料。因為這個屬性只是一個簡單的標誌類型的屬性。
列表 D
Class Hide : System.Attribute
{
//This is a simple attribute, that only requires
// the default constructor.
}
從代碼中讀取屬性
讀取屬性並檢查其中的資料比使用屬性或建立屬性顯著地更加複雜。讀取屬性要求開發人員要對如何使用一個對象的反射資訊有個基本瞭解。如果你不熟悉反射機制,可以閱讀“應用反射”系列文章。
假設我們正在查看一個類,我們想知道該類的那個properties使用了Alias屬性以及都有哪些別名。列表E實現了這個功能。
列表 E
Private Dictionary<string, string> GetAliasListing(Type destinationType)
{
//Get all the properties that are in the
// destination type.
PropertyInfo[] destinationProperties = destinationType.GetProperties();
Dictionary<string, string> aliases = newDictionary<string, string>();
for each (PropertyInfo property in destinationProperties)
{
//Get the alias attributes.
object[] aliasAttributes =
property. GetCustomAttributes( typeof(Alias), true);
//Loop through the alias attributes and
// add them to the dictionary.
foreach (object attribute in aliasAttributes)
foreach (string name in ((Alias)attribute).Names)
aliases.Add(name, property.Name);
//We also need to add the property name
// as an alias.
aliases.Add(property.Name, property.Name);
}
return aliases;
}
這段代碼最重要的地方是對GetCustomAttributes的調用以及迴圈遍曆屬性提取別名的地方。
GetCustomAttributes方法可以在我們從物件類型中提取的PropertyInfo類中找到。在上面的應用中,我們將要查詢的屬性類型作為參數傳給GetCustomAttributes方法,同時還將“true”傳遞給該方法使得可以列出繼承的屬性。如何發現匹配的屬性,GetCustomAttributes方法將返回一個對象數組。還有另外一種超負荷方法可以列出property上的所有屬性,而不管屬性類型是什麼。
一旦有了屬性,我們需要檢查它們並從中提取需要的資訊。這可以通過遍曆由GetCustomAttributes方法得到的對象數組並將每個對象映射成我們要查詢的屬性來完成。在完成映射後,我們就可以像訪問任意其它類的properties一樣來訪問屬性的properties。
正如我在前面所說,讀取屬性是最困難的部分。然而,一旦我們寫後讀取屬性的代碼後,將來回憶和實施起來就相當容易了。
應用樣本
我強烈建議你下載本文包含的這個應用樣本。這個應用樣本在一個簡單的Windows應用中實現了下面的屬性,並示範了如何讀取和使用它們。
- Alias——這同上面提到的Alias屬性一樣。當你需要將一種類型的對象翻譯為另一種類型時,需要使用該屬性。例如,如果你有一個Customer對象和一個Address對象,你可能需要將它們都翻譯為一個合并的包含人名和地址的Person對象,當一個不能使用直接映射時,可以使用該屬性。
- DisplayName——範例程式碼中包括檢查一個類執行個體並將它的property名稱與值輸出到螢幕上的代碼。這個屬性可用於覆蓋送到螢幕顯示的property名稱。例如,一個名為“FName”property可以使用DisplayName屬性,因此它顯示為“First Name”。
- Examine——這個屬性使得樣本應用中的PrintObject方法進入更深一層,並輸出使用了Examine屬性的property的值。例如,樣本應用中的Customer對象將Examine屬性應用到Address property。這將指示PrintObject方法輸出address property中的所有資訊。
- Hide——這個屬性指示PrintObject()方法不要將當前property輸出到螢幕。該屬性用在Customer對象的SSN property上。
樣本應用中包含了實現和讀取屬性每一步的注釋,仔細看一下,我敢保證你會發現一些在自己的應用中可以利用的功能。