白話C#:特性
首先要說的是,可能一些剛接觸C#的朋友常常容易把屬性(Property)跟特性(Attribute)弄混淆,其實這是兩種不同的東西。屬性就是面向對象思想裡所說的封裝在類裡面的資料字段,其形式為:
1:
public
class
HumanBase
2:
{
3:
public
string
Name { get; set; }
4:
public
int
Age { get; set; }
5:
public
int
Gender { get; set; }
6:
}
在HumanBase這個類裡出現的字段都叫屬性(Property),而特性(Attribute)又是怎樣的呢?
1:
[Serializable]
2:
public
class
HumanBase
3:
{
4:
public
string
Name { get; set; }
5:
public
int
Age { get; set; }
6:
public
int
Gender { get; set; }
7:
}
簡單地講,我們在HumanBase類聲明的上一行加了一個[Serializable],這就是特性(Attribute),它表示HumanBase是可以被序列化的,這對于網絡傳輸是很重要的,不過你不用擔心如何去了解它,如何了解就是我們下面要探讨的。
C#的特性可以應用于各種類型和成員。前面的例子将特性用在類上就可以被稱之為“類特性”,同理,如果是加在方法聲明前面的就叫方法特性。無論它們 被用在哪裡,無論它們之間有什麼差別,特性的最主要目的就是自描述。并且因為特性是可以由自己定制的,而不僅僅局限于.NET提供的那幾個現成的,是以給 C#程式開發帶來了相當大的靈活性和便利。
我們還是借用生活中的例子來介紹C#的特性機制吧。
假設有一天你去坐飛機,你就必須提前去機場登機處換登機牌。登機牌就是一張紙,上面寫着哪趟航班、由哪裡飛往哪裡以及你的名字、座位号等等資訊,其 實,這就是特性。它不需要你生理上包含這些屬性(人類出現那會兒還沒飛機呢),就像上面的HumanBase類沒有IsSerializable這樣的屬 性,特性隻需要在類或方法需要的時候加上去就行了,就像你不總是在天上飛一樣。
當我們想知道HumanBase是不是可序列化的,可以通過:
1:
static
void
Main(string
[] args)
2:
{
3:
Console.WriteLine(typeof
(HumanBase).IsSerializable);
4:
5:
Console.ReadLine();
6:
}
拿到了登機牌,就意味着你可以合法地登機起飛了。但此時你還不知道你要坐的飛機停在哪裡,不用擔心,地勤人員會開車送你過去,但是他怎麼知道你是哪趟航班的呢?顯然還是通過你手中的登機牌。是以,特性最大的特點就是自描述。
既然是起到描述的作用,那目的就是在于限定。就好比地勤不會把你随便拉到一架飛機跟前就扔上去了事,因為标簽上的說明資訊就是起到限定的作用,限定 了目的地、乘客和航班,任何差錯都被視為異常。如果前面的HumanBase不加上Serializable特性就不能在網絡上傳輸。
我們在順帶來介紹一下方法特性,先給HumanProperty加上一個Run方法:
1:
[Serializable]
2:
public
class
HumanPropertyBase
3:
{
4:
public
string
Name { get; set; }
5:
public
int
Age { get; set; }
6:
public
int
Gender { get; set; }
7:
8:
public
void
Run(int
speed)
9:
{
10:
// Running is good for health.
11:
}
12:
}
隻要是個四肢健全、身體健康的人就可以跑步,那這麼說,跑步就是有前提條件的,至少是四肢健全,身體健康。由此可見,殘障人士和老年人如果跑步就會出 問題。假設一個HumanBase的對象代表的是一位耄耋老人,如果讓他當劉翔的陪練,那就直接光榮了。如何避免這樣的情況呢,我們可以在Run方法中加 一段邏輯代碼,先判斷Age大小,如果小于2或大于60直接抛異常,但是2-60歲之間也得用Switch來分年齡階段地判斷speed參數是否合适,那 麼邏輯就相當臃腫。簡而言之,如何用特性表示一個方法不能被使用呢?OK, here we go:
1:
[Obsolete("I'm so old, don't kill me!"
, true
)]
2:
public
virtual
void
Run(int
speed)
3:
{
4:
// Running is good for health.
5:
}
上面大緻介紹了一下特性的使用與作用,接下來我們要向大家展示的是如何通過自定義特性來提高程式的靈活性,如果特性機制僅僅能使用.NET提供的那幾種特性,不就太不過瘾了麼。
首先,特性也是類。不同于其它類的是,特性都必須繼承自System.Attribute類,否則編譯器如何知道誰是特性誰是普通類呢。當編譯器檢 測到一個類是特性的時候,它會識别出其中的資訊并存放在中繼資料當中,僅此而已,編譯器并不關心特性說了些什麼,特性也不會對編譯器起到任何作用,正如航空 公司并不關心每個箱子要去哪裡,隻有箱子的主人和搬運工才會去關心這些細節。假設我們現在就是航空公司的管理人員,需要設計出前面提到的登機牌,那麼很簡 單,我們先看看最主要的資訊有哪些:
1:
public
class
BoardingCheckAttribute : Attribute
2:
{
3:
public
string
ID { get; private
set; }
4:
public
string
Name { get; private
set; }
5:
public
int
FlightNumber { get; private
set; }
6:
public
int
PostionNumber { get; private
set; }
7:
public
string
Departure { get; private
set; }
8:
public
string
Destination { get; private
set; }
9:
}
我們簡單列舉這些屬性作為航空公司登機牌上的資訊,用法和前面的一樣,貼到HumanBase上就行了,說明此人具備登機資格。這裡要簡單提一下, 你可能已經注意到了,在使用BoardingCheckAttribute的時候已經把Attribute省略掉了,不用擔心,這樣做是對的,因為編譯器 預設會自己加上然後查找這個屬性類的。哦,等一下,我突然想起來他該登哪架飛機呢?顯然,在這種需求下,我們的特性還沒有起到應有的作用,我們還的做點兒 工作,否則乘客面對一張空白的機票一定會很迷茫。
于是,我們必須給這個特性加上構造函數,因為它不僅僅表示登機的資格,還必須包含一些必要的資訊才行:
1:
public
BoardingCheckAttribute(string
id, string
name, string
flightNumber, string
positionNumber, string
departure, string
destination)
2:
{
3:
this
.ID = id;
4:
this
.Name = name;
5:
this
.FlightNumber = flightNumber;
6:
this
.PositionNumber = positionNumber;
7:
this
.Departure = departure;
8:
this
.Destination = destination;
9:
}
OK,我們的乘客就可以拿到一張正式的登機牌登機了,have a good flight!
1:
static
void
Main(string
[] args)
2:
{
3:
BoardingCheckAttribute boardingCheck = null
;
4:
object
[] customAttributes = typeof
(HumanPropertyBase).GetCustomAttributes(true
);
5:
6:
foreach
(var attribute in
customAttributes)
7:
{
8:
if
(attribute is
BoardingCheckAttribute)
9:
{
10:
boardingCheck = attribute as
BoardingCheckAttribute;
11:
12:
Console.WriteLine(boardingCheck.Name
13:
+ "'s ID is "
14:
+ boardingCheck.ID
15:
+ ", he/she wants to "
16:
+ boardingCheck.Destination
17:
+ " from "
18:
+ boardingCheck.Departure
19:
+ " by the plane "
20:
+ boardingCheck.FlightNumber
21:
+ ", his/her position is "
22:
+ boardingCheck.PositionNumber
23:
+ "."
);
24:
}
25:
}
26:
27:
Console.ReadLine();
28:
}