使用屬性 (C# 程式設計手冊)
更新:2007 年 11 月
屬性 (Property) 是欄位和方法的綜合體;如果是物件的使用者,屬性會以欄位出現,存取屬性需要相同的語法。而對於類別的實作者,屬性則是一或兩個程式碼區塊,代表 get 存取子 (Accessor) 和 (或) set 存取子。get 存取子的程式碼區塊會於讀取屬性時執行,而且 set 存取子的程式碼區塊會在指派新值給該屬性時執行。不含 set 存取子的屬性會被視為唯讀,而不含 get 存取子的屬性會被視為唯寫;同時具有這兩種存取子的屬性則為可讀寫。
屬性並不會歸類為變數,這點與欄位不同。因此,您無法將屬性當成 ref (C# 參考) 或 out (C# 參考) 參數進行傳遞。
屬性能有許多用途:可以在允許變更之前驗證資料;可以在實際從其他某些來源 (例如資料庫) 擷取資料時,透明地在某一類別上公開資料;也可以在資料變更 (例如引發事件,或是變更其他欄位的值) 時採取動作。
屬性在類別區塊內宣告的方式是指定欄位存取層級、接著指定屬性的型別、再指定屬性的名稱,然後是宣告 get 存取子和 (或) set 存取子的程式碼區塊。例如:
public class Date
{
private int month = 7; // Backing store
public int Month
{
get
{
return month;
}
set
{
if ((value > 0) && (value < 13))
{
month = value;
}
}
}
}
在這個範例中,Month 會宣告為屬性,讓 set 存取子可以確定 Month 值是介於 1 到 12 之間。Month 屬性會使用私用欄位來追蹤實際的值。屬性資料的實際位置通常稱為屬性的「支援的存放區」,屬性經常會使用私用欄位做為支援的存放區,而欄位之所以標記為私用的原因,是要確保一定要透過呼叫屬性才能夠變更欄位。如需公用和私用存取限制的詳細資訊,請參閱存取修飾詞 (C# 程式設計手冊)。
自動實作的屬性為簡單屬性宣告提供了簡化的語法。如需詳細資訊,請參閱自動實作的屬性 (C# 程式設計手冊)。
get 存取子
get 存取子的主體就像方法的主體。它必須傳回屬性型別的值。執行 get 存取子的意思就是等於讀取欄位值;例如,當您從 get 存取子傳回私用變數,並啟用最佳化時,編譯器就會內嵌對 get 存取子方法的呼叫,如此即可避免方法呼叫額外負荷。然而,由於在編譯時期,編譯器不知道在執行階段可以確實呼叫哪些方法,因此,無法內嵌虛擬的 get 存取子方法。下列是一個 get 存取子,它會傳回私用欄位 name 的值:
class Person
{
private string name; // the name field
public string Name // the Name property
{
get
{
return name;
}
}
}
當您參考屬性時,除了指派的目標之外,還會叫用 get 存取子來讀取屬性值,例如:
Person person = new Person();
//...
System.Console.Write(person.Name); // the get accessor is invoked here
get 存取子必須在 return 或 throw 陳述式結束,而且控制項不能超出存取子的主體。
使用 get 存取子來變更物件狀態是不好的程式設計作法,例如,每一次存取 number 欄位時,下列存取子會造成物件狀態變更的不良結果。
private int number;
public int Number
{
get
{
return number++; // Don't do this
}
}
get 存取子可以用於傳回欄位值,或予以計算並且傳回。例如:
class Employee
{
private string name;
public string Name
{
get
{
return name != null ? name : "NA";
}
}
}
在之前的程式碼片段中,如果您沒有將值指派給 Name 屬性,它會傳回 NA 值。
set 存取子
set 存取子就像傳回型別為 void 的方法。它使用稱為 value 的隱含參數,其型別是屬性的型別。在下列範例中,會將 set 存取子加入 Name 屬性:
class Person
{
private string name; // the name field
public string Name // the Name property
{
get
{
return name;
}
set
{
name = value;
}
}
}
當您將值指派給屬性,會以提供新值的引數來叫用 set 存取子。例如:
Person person = new Person();
person.Name = "Joe"; // the set accessor is invoked here
System.Console.Write(person.Name); // the get accessor is invoked here
在 set 存取子中,區域變數宣告使用隱含參數名稱 value 是錯誤的。
備註
屬性可以標記為 public、private、protected、internal 或 protected internal,這些存取修飾詞 (Modifier) 將定義類別使用者如何存取屬性。相同屬性的 get 和 set 存取子可能具有不同的存取修飾詞。例如,get 可能具有 public,以允許來自型別外部的唯讀存取,而 set 則可能具有 private 或 protected。如需詳細資訊,請參閱存取修飾詞 (C# 程式設計手冊)。
您可以使用 static 關鍵字,將屬性宣告為靜態屬性。這讓呼叫端即使沒有類別的執行個體,也可以隨時使用屬性。如需詳細資訊,請參閱靜態類別和靜態類別成員 (C# 程式設計手冊)。
屬性可以使用 virtual 關鍵字標記為虛擬屬性。如此一來,衍生類別就可以使用 override 關鍵字覆寫屬性行為。如需這些選項的詳細資訊,請參閱繼承 (C# 程式設計手冊)。
用來覆寫虛擬屬性的屬性也可以是 sealed,對於衍生類別來說,該屬性即不再為虛擬的。最後,屬性可以宣告為 abstract。這表示類別中不會有實作,而衍生類別必須撰寫本身的實作。如需這些選項的詳細資訊,請參閱抽象和密封類別以及類別成員 (C# 程式設計手冊)。
注意事項: |
---|
在 static 屬性的存取子上,使用 virtual (C# 參考)、abstract (C# 參考) 或 override (C# 參考) 修飾詞是錯誤的。 |
範例
這個範例示範執行個體、靜態和唯讀屬性;該屬性會接受從鍵盤輸入的員工名稱、以 1 遞增 NumberOfEmployees,及顯示員工名稱和編號。
public class Employee
{
public static int NumberOfEmployees;
private static int counter;
private string name;
// A read-write instance property:
public string Name
{
get { return name; }
set { name = value; }
}
// A read-only static property:
public static int Counter
{
get { return counter; }
}
// A Constructor:
public Employee()
{
// Calculate the employee's number:
counter = ++counter + NumberOfEmployees;
}
}
class TestEmployee
{
static void Main()
{
Employee.NumberOfEmployees = 107;
Employee e1 = new Employee();
e1.Name = "Claude Vige";
System.Console.WriteLine("Employee number: {0}", Employee.Counter);
System.Console.WriteLine("Employee name: {0}", e1.Name);
}
}
/* Output:
Employee number: 108
Employee name: Claude Vige
*/
這個範例示範如何存取基底類別中的屬性,該屬性被衍生類別中名稱相同的另一個屬性所隱藏。
public class Employee
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
public class Manager : Employee
{
private string name;
// Notice the use of the new modifier:
public new string Name
{
get { return name; }
set { name = value + ", Manager"; }
}
}
class TestHiding
{
static void Main()
{
Manager m1 = new Manager();
// Derived class property.
m1.Name = "John";
// Base class property.
((Employee)m1).Name = "Mary";
System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
}
}
/* Output:
Name in the derived class is: John, Manager
Name in the base class is: Mary
*/
以下是上述範例中的重點:
衍生類別中的 Name 屬性會隱藏基底類別中的 Name 屬性,在這種情況下,new 修飾詞是用於衍生類別裡的屬性宣告中:
public new string Name
轉換 (Employee) 是用來存取基底類別中的隱藏屬性:
((Employee)m1).Name = "Mary";
如需隱藏成員的詳細資訊,請參閱 new 修飾詞 (C# 參考)。
在這個範例裡,Cube 和 Square 兩個類別會實作 Shape 抽象類別 (Abstract Class),並且覆寫它的抽象 Area 屬性。請注意 override 修飾詞在屬性上的用法。該程式會接受邊長的輸入值,然後計算正方形和立方體的面積,而且,還也會接受面積的輸入值,然後計算正方形和立方體的對應邊長。
abstract class Shape
{
public abstract double Area
{
get;
set;
}
}
class Square : Shape
{
public double side;
public Square(double s) //constructor
{
side = s;
}
public override double Area
{
get
{
return side * side;
}
set
{
side = System.Math.Sqrt(value);
}
}
}
class Cube : Shape
{
public double side;
public Cube(double s)
{
side = s;
}
public override double Area
{
get
{
return 6 * side * side;
}
set
{
side = System.Math.Sqrt(value / 6);
}
}
}
class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());
// Compute the areas:
Square s = new Square(side);
Cube c = new Cube(side);
// Display the results:
System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
System.Console.WriteLine();
// Input the area:
System.Console.Write("Enter the area: ");
double area = double.Parse(System.Console.ReadLine());
// Compute the sides:
s.Area = area;
c.Area = area;
// Display the results:
System.Console.WriteLine("Side of the square = {0:F2}", s.side);
System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
}
}
/* Example Output:
Enter the side: 4
Area of the square = 16.00
Area of the cube = 96.00
Enter the area: 24
Side of the square = 4.90
Side of the cube = 2.00
*/