使用屬性 (C# 程式設計手冊)
屬性會合併欄位和方法的各個層面。 對物件的使用者而言,屬性會呈現為欄位,而存取屬性需要相同的語法。 對於類別的實作器而言,屬性是一或兩個程式碼區塊,代表 get
存取子和 set
(或) init
存取子。 讀取屬性時,會執行 get
存取子的程式碼區塊;指派屬性的值時,會執行 set
或 init
存取子的程式碼區塊。 沒有 set
存取子的屬性會視為唯寫。 沒有 get
存取子的屬性則視為唯寫。 具有這兩個存取子的屬性是讀寫。 您可以使用 init
存取子,而不是 set
存取子,讓屬性設定為物件初始化的一部分,否則使其成為唯讀。
與欄位不同,屬性不會分類為變數。 因此,您無法將屬性傳遞為 ref
或 out
參數。
屬性有許多用途:
- 他們可以在允許變更之前先驗證資料。
- 其可以在類別上以透明方式公開資料,其中該資料是從一些其他來源 (例如資料庫) 擷取。
- 當資料變更時,他們可以採取動作,例如引發事件,或變更其他欄位的值。
在類別區塊中宣告屬性的方式是指定欄位存取層級,其後依序接著屬性類型、屬性名稱,以及宣告 get
存取子和 (或) set
存取子的程式碼區塊。 例如:
public class Date
{
private int _month = 7; // Backing store
public int Month
{
get => _month;
set
{
if ((value > 0) && (value < 13))
{
_month = value;
}
}
}
}
在此範例中,將 Month
宣告為屬性,因此,set
存取子可以確定設定的 Month
值介於 1 與 12 之間。 Month
屬性使用私用欄位來追蹤實際值。 屬性資料的實際位置通常稱為屬性的「備份存放區」。屬性經常使用私人欄位作為備份存放區。 此欄位標示為私用,以確認只有呼叫屬性才能進行變更。 如需公用和私用存取限制的詳細資訊,請參閱存取修飾詞。 自動實作的屬性提供簡單屬性宣告的簡化語法。 如需詳細資訊,請參閱 自動實作的屬性。
從 C# 13 開始,您可以使用欄位支援的屬性,將驗證新增至set
自動實作屬性的存取子,如下列範例所示:
public class DateExample
{
public int Month
{
get;
set
{
if ((value > 0) && (value < 13))
{
field = value;
}
}
}
}
重要
關鍵詞 field
是 C# 13 中的預覽功能。 您必須使用 .NET 9,並將項目 <LangVersion>
檔中的 元素設定為 preview
,才能使用 field
內容關鍵詞。
您應該小心在 field
類別中使用關鍵詞功能,其具有名為 field
的欄位。 新的 field
關鍵詞會遮蔽屬性存取子範圍中名為 field
的欄位。 您可以變更變數的名稱 field
,或使用 @
權杖將識別元參考 field
為 @field
。 您可以閱讀 關鍵詞的功能規格field
來深入瞭解。
Get 存取子
get
存取子的主體與方法的主體類似。 它必須傳回屬性類型的值。 C# 編譯器和 Just-in-time (JIT) 編譯器會偵測實作 get
存取子的常見模式,並最佳化這些模式。 例如,傳回欄位而不執行任何計算的 get
存取子可能會最佳化為該欄位的記憶體讀取。 自動實作的屬性會遵循此模式,並受益於這些優化。 不過,因為編譯器在編譯時期內不知道執行階段實際呼叫的方法,所以無法內嵌虛擬 get
存取子方法。 下列範例示範 get
存取子會傳回私用欄位 _name
的值:
class Employee
{
private string _name; // the name field
public string Name => _name; // the Name property
}
當您參考指派目標以外的屬性時,會叫用 get
存取子來讀取屬性的值。 例如:
var employee= new Employee();
//...
System.Console.Write(employee.Name); // the get accessor is invoked here
get
存取子必須為運算式主體成員,或結束於 return 或 throw 陳述式,且控制項無法離開存取子主體。
警告
使用 存取子來變更物件 get
狀態通常是錯誤的程式設計樣式。 此規則的其中一個例外是延遲評估的屬性,只有在第一次存取屬性時,才會計算屬性的值。
get
存取子可用來傳回欄位值或計算它,並傳回它。 例如:
class Manager
{
private string _name;
public string Name => _name != null ? _name : "NA";
}
在上一個範例中,如果您未指派 Name
屬性的值,則會傳回 NA
值。
Set 存取子
set
存取子與傳回型別為 void 的方法類似。 它使用稱為 value
的隱含參數,其類型為屬性的類型。 編譯器和 JIT 編譯器也會辨識 set
或 init
存取子的常見模式。 這些常見的模式已經過最佳化,直接寫入支援欄位的記憶體。 在下列範例中,將 set
存取子新增至 Name
屬性:
class Student
{
private string _name; // the name field
public string Name // the Name property
{
get => _name;
set => _name = value;
}
}
當您指派屬性的值時,會使用提供新值的引數來叫用 set
存取子。 例如:
var student = new Student();
student.Name = "Joe"; // the set accessor is invoked here
System.Console.Write(student.Name); // the get accessor is invoked here
在 set
存取子中使用區域變數宣告的隱含參數名稱 value
是錯誤的。
Init 存取子
建立 init
存取子的程式碼與建立 set
存取子的程式碼相同,不過您會使用 init
關鍵字,而不是 set
。 差異在於,init
存取子只能在建構函式中使用,或透過物件初始設定式使用。
備註
屬性可標記為 public
、private
、protected
、internal
、protected internal
或 private protected
。 這些存取修飾詞定義類別使用者如何存取屬性。 相同屬性的 get
和 set
存取子可能會有不同的存取修飾詞。 例如,get
可能是 public
以允許從類型外部進行唯讀存取,而 set
可能是 private
或 protected
。 如需詳細資訊,請參閱存取修飾詞。
屬性可以使用 static
關鍵字宣告為靜態屬性。 靜態屬性隨時可供呼叫端使用,即使沒有任何類別執行個體也是一樣。 如需詳細資訊,請參閱靜態類別和靜態類別成員。
屬性可以使用 virtual 關鍵字標示為虛擬屬性。 虛擬屬性可讓衍生類別使用 override 關鍵字覆寫屬性行為。 如需這些選項的詳細資訊,請參閱繼承。
覆寫虛擬屬性的屬性也可為 sealed,指定它對衍生類別而言不再是虛擬。 最後,屬性可以宣告為 abstract。 抽象屬性不會在類別中定義實作,且衍生類別必須撰寫自己的實作。 如需這些選項的詳細資訊,請參閱抽象和密封類別以及類別成員。
範例
此範例示範執行個體、靜態和唯讀屬性。 它會從鍵盤接受員工姓名、將 NumberOfEmployees
增加 1,並顯示員工姓名和編號。
public class Employee
{
public static int NumberOfEmployees;
private static int _counter;
private string _name;
// A read-write instance property:
public string Name
{
get => _name;
set => _name = value;
}
// A read-only static property:
public static int Counter => _counter;
// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}
隱藏屬性範例
此範例示範如何存取基底類別中由衍生類別中同名的另一個屬性所隱藏的屬性:
public class Employee
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
public class Manager : Employee
{
private string _name;
// Notice the use of the new modifier:
public new string Name
{
get => _name;
set => _name = value + ", Manager";
}
}
class TestHiding
{
public static void Test()
{
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 修飾詞。
覆寫屬性範例
在此範例中,Cube
和 Square
這兩個類別會實作抽象類別 Shape
,並覆寫其抽象 Area
屬性。 請注意如何在屬性上使用 override 修飾詞。 程式接受這端作為輸入,並計算正方形和立方體的區域。 它也接受區域作為輸入,並計算正方形和立方體的對應端。
abstract class Shape
{
public abstract double Area
{
get;
set;
}
}
class Square : Shape
{
public double side;
//constructor
public Square(double s) => side = s;
public override double Area
{
get => side * side;
set => side = System.Math.Sqrt(value);
}
}
class Cube : Shape
{
public double side;
//constructor
public Cube(double s) => side = s;
public override double Area
{
get => 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
*/