屬性擁有兩個類似於函數的塊,一個塊用於獲取屬性的值,另一個塊用於設置屬性的值。這兩個塊也稱為訪問器,分別用get和set關鍵字來定義,可以用於控制對屬性的訪問級別。可以忽略其中的一個塊來創建只讀或只寫屬性(忽略get塊創建只寫屬性,忽略set塊創建只讀屬性)。當然,這僅適用於外部代碼,因為類中的其他代碼可以訪問的數據。還可以在訪問器上包含可訪問修飾符,例如使get塊變成公共的,把set塊變成保護的。只有包含其中一個一個塊,才能獲得有效屬性(既不能讀取也不能修改的屬性沒有任何用途)。
屬性的基本結構包括標准的可訪問修飾符(public、private等),后跟類名、屬性和get塊(或set塊,或者get塊和set塊,其中包含屬性處理代碼)。
public int myIntProp
{
get
{
//Property get code.
}
set
{
//Property set code.
}
}
1 get關鍵字
get塊必須有一個屬性的返回值,簡單的屬性一般與私有字段相關聯,以控制對這個字段的訪問,此時get塊可以直接返回該字段的值,例如:
private int myInt;
public int myIntProp
{
get
{
return myInt;
}
set
{
//Property set code.
}
}
類外部的代碼不能直接訪問這個myInt字段,私有的,必須使用屬性來訪問該字段。
2 set關鍵字
set函數以類似的方法把一個值賦給字段。這里使用value表示用戶提供的屬性值:
private int myInt;
public int myIntProp
{
get
{
return myInt;
}
set
{
myInt = value;
}
}
value等於類似與屬性相同的值,所以如果屬性和字段使用相同的類型,就不必擔心數據類型轉換了。
這個簡單的屬性只能直接訪問myInt字段。在對操作進行更多的控制的時候,屬性的真正作用才能發揮出來,例如,使用下面的代碼實現set塊:
set
{
if(value >= 0 && value <= 10)
myInt = value;
}
只用賦給屬性的值在1~10之間,才會改myInt。此時,要做一個重要的設計選擇:如果使用了無效值,該怎么辦:
- 什么也不做
- 給字段賦默認值
- 繼續執行,就好像沒有發生錯誤一樣,但記錄下來該事件,以備將來分析
- 拋出異常
一般情況下,后面兩個選擇效果較好,選擇哪個選項取決於如何使用類,以及給用戶授予多少控制權。拋出異常給用戶提供的控制權相當的大,例如:
set
{
if(value >= 0 && value <= 10)
myInt = value;
else
throw (new ArgumentOutOfRangeException("myIntProp",value,"myIntProp must be assigned a value between 0 and 10."))
}
這可以在使用屬性的代碼中通過try...catch...finaly邏輯來處理。
注:屬性可以使用virtual、override和abstract關鍵字,就像方法一樣,但這幾個關鍵字不能用於字段。最后,如上述,訪問器可以有自己的訪問性。
實例:
public class MyClass
{
public readonly string Name;
private int intVal;
public int Val
{
get
{
return intVal;
}
set
{
if (value >= 0 && value <= 10 )
intVal = value;
else
throw (new ArgumentOutOfRangeException("Val",value,"Val must be assigned a value between 0 ang 10."));
}
}
public override string ToString()
{
return "Name:"+Name+"\nVal:"+Val;
}
private MyClass(): this("Default Name")
{
}
public MyClass(string newName)
{
Name = newName;
intVal = 0;
}
}
static void Main(string[] args)
{
Console.WriteLine("Creating object myobj...");
MyClass myObj = new MyClass("My Object");
Console.WriteLine("myObj created.");
for (int i = -1; i <= 0; i++ )
{
try
{
Console.WriteLine("\nAttempting to assign {0} to myObj.val...",i);
myObj.Val = i;
Console.WriteLine("Value {0} assigned to myObj.val.", myObj.Val);
}
catch(Exception e)
{
Console.WriteLine("Exception {0} throw.",e.GetType().FullName);
Console.WriteLine("Message:\n\"{0}\"",e.Message);
}
}
Console.WriteLine("\nOutputting myObj.ToString()...");
Console.WriteLine(myObj.ToString());
Console.WriteLine("myObj.ToString() Output.");
Console.ReadKey();
}
Main()中的的代碼創建並使用在MyClass.cs中定義的MyClass類的實例。實例化這個類必須使用非默認的構造函數來進行,因為MyClass類的默認構造函數是私有的。
Main()試着給myObj(MyClass的實例)的Val屬性賦值。for循環在兩次中賦值-1和0,try..catch...結構用於檢測拋出的異常。把-1賦給屬性時,會拋出System.ArgumentOutOfException類型的異常,catch塊中的代碼會把改異常的信息輸出到控制台窗口中。在下一個循環中,值0成功的賦給了Val屬性,通過這個屬性再把值賦給私有字段intVal。
3 自動屬性
屬性是訪問對象狀態的首選方式,因為他們禁止外部代碼實現對象內部的數據存儲機制。屬性還對內部數據的訪問事施加了更多的控制,但是,一般以非常標准的方式屬性,即通過一個公共屬性來訪問一個私有成員。其代碼非常類似於前面的代碼,這是VS重構工具自動生成的。
重構功能肯定加快了鍵入的速度,C#還為此提供了另一種方式:自動屬性。利用自動屬性,可以用簡化的語法聲明屬性,C#編譯器會自動添加未鍵入的內容,具體而言,編譯器會聲明一個用於存儲屬性的私有字段,並在屬性的get和set塊中使用該字段,我們無需考慮細節。
public int MyIntProp
{
get;
set;
}
我們按照通常的方式定義屬性的可訪問性、類型和名稱。但是沒有給get和set塊提供實現的代碼。這些塊的實現代碼(和底層的字段)由編譯器提供。
使用自動屬性時,只能通過屬性訪問數據,不能通過底層的私有字段來訪問,我們不知道底層私有字段的名稱(該名稱是編譯期間定義的)。但這並不是一個真正意義上的限制,因為可以直接使用屬性名。自動屬性的唯一限制是他們必須包含get和set存儲器,無法使用這種方法定義只讀和只寫屬性。