屬性 (C# 程式設計手冊)
屬性是一種提供彈性機制來讀取、寫入或運算資料欄位值的成員。 屬性會顯示為公用資料成員,但它們會以名為存取子的特殊方法來實作。 此功能可讓呼叫端容易存取資料,同時有助於提升資料的安全性和彈性。 屬性的語法是欄位的自然延伸。 欄位可定義儲存位置:
public class Person
{
public string? FirstName;
// Omitted for brevity.
}
自動實作的屬性
屬性定義包含 get
和 set
存取子的宣告,以擷取和指派該屬性的值:
public class Person
{
public string? FirstName { get; set; }
// Omitted for brevity.
}
上述範例顯示 自動實作的屬性。 編譯器會產生屬性的隱藏支援欄位。 編譯器也會實作 get
和 set
存取子的主體。 任何屬性都會套用至自動實作的屬性。 您可以對屬性指定 field:
標籤,藉此將屬性套用至編譯器產生的支援欄位。
您可以將屬性初始化為預設值以外的值,方法是在屬性的右大括弧後方設定值。 您可能偏好將 FirstName
屬性的初始值設為空字串,而非 null
。 您會如下列程式碼所示指定該值:
public class Person
{
public string FirstName { get; set; } = string.Empty;
// Omitted for brevity.
}
欄位支援的屬性
在 C# 13 中,您可以使用關鍵字預覽功能,在屬性 field
的存取子中新增驗證或其他邏輯。 field
關鍵詞會存取屬性的編譯程式合成支援欄位。 它可讓您撰寫屬性存取子,而不需要明確宣告個別的備份欄位。
public class Person
{
public string? FirstName
{
get;
set => field = value.Trim();
}
// Omitted for brevity.
}
重要
關鍵詞 field
是 C# 13 中的預覽功能。 您必須使用 .NET 9,並將項目 <LangVersion>
檔中的 元素設定為 preview
,才能使用 field
內容關鍵詞。
您應該小心在 field
類別中使用關鍵詞功能,其具有名為 field
的欄位。 新的 field
關鍵詞會遮蔽屬性存取子範圍中名為 field
的欄位。 您可以變更變數的名稱 field
,或使用 @
權杖將識別元參考 field
為 @field
。 您可以閱讀 關鍵詞的功能規格field
來深入瞭解。
必要屬性
前面的範例允許呼叫端使用預設建構函式建立 Person
,無需設定屬性 FirstName
。 屬性將型別變更為可為 Null 的字串。 從 C# 11 開始,您可以要求呼叫端設定屬性:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName) => FirstName = firstName;
public required string FirstName { get; init; }
// Omitted for brevity.
}
前述程式碼對 Person
類別進行了兩項更改。 FirstName
屬性宣告包含 required
修飾元。 也就是說,任何建立新的 Person
的程式碼都必須使用物件初始設定式來設定此屬性。 其次,採用 firstName
參數的建構函式具有 System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute 屬性。 這項屬性會通知編譯器:此建構函式會設定「所有」required
成員。 以物件初始設定式來設定 required
屬性,並不需要呼叫端使用此建構函式。
重要
請勿將 required
與「不可為 Null」混淆。 您可將 required
屬性設定為 null
或 default
。 如果類型不可為 Null (例如這些範例中的 string
),則編譯器會發出警告。
var aPerson = new Person("John");
aPerson = new Person{ FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();
運算式主體定義
屬性存取子通常由單行陳述式組成。 存取子會指派或傳回表達式的結果。 您可以將這些屬性實作為運算式主體成員。 運算式主體定義包含 =>
權杖,後面接著要從屬性指派或擷取的運算式。
唯讀屬性可將 get
存取子實作為運算式主體成員。 下列範例會將唯讀的 Name
屬性實作為運算式主體成員:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public required string FirstName { get; init; }
public required string LastName { get; init; }
public string Name => $"{FirstName} {LastName}";
// Omitted for brevity.
}
Name
屬性是經過運算的屬性。 Name
沒有支援欄位。 該屬性每次都會運算。
存取控制
上述範例所示為讀取/寫入屬性。 您也可以建立唯讀屬性,或是為 set 和 get 存取子提供不同的存取範圍。 假設您的 Person
類別應該只啟用從 類別中的其他方法變更 屬性的值 FirstName
。 您可以提供 set 存取子 private
輔助功能,而不是 internal
或 public
:
public class Person
{
public string? FirstName { get; private set; }
// Omitted for brevity.
}
FirstName
屬性可從任何程式碼讀取,但只能從 Person
類別的程式碼指派。
您可以將任何嚴格的存取修飾詞新增至 set 或 get 存取子。 個別存取子的存取修飾符必須比屬性的存取權更嚴格。 由於 FirstName
屬性為 public
,但 set 存取子為 private
,因此上述程式碼合法。 您無法使用 public
存取子宣告 private
屬性。 屬性宣告也可以宣告為 protected
、internal
、protected internal
,甚至是 private
。
set
存取子有兩個特殊存取修飾詞:
set
存取子可將init
當作存取修飾詞。 該set
存取子只能從物件初始設定式或型別的建構函式呼叫。 它比set
存取子的private
更嚴格。- 自動實作的屬性可以宣告
get
沒有存取子的set
存取子。 在此情況下,編譯器只允許從型別的建構函式呼叫set
存取子。 它比set
存取子的init
存取子更嚴格。
修改 Person
類別,如下所示:
public class Person
{
public Person(string firstName) => FirstName = firstName;
public string FirstName { get; }
// Omitted for brevity.
}
上述範例需要呼叫端使用包含 FirstName
參數的建構函式。 呼叫端無法使用物件初始設定式將值指派給屬性。 若要支援初始設定式,您可以將 set
存取子設為 init
存取子,如下列程式碼所示:
public class Person
{
public Person() { }
public Person(string firstName) => FirstName = firstName;
public string? FirstName { get; init; }
// Omitted for brevity.
}
這些修飾詞通常與 required
修飾詞搭配使用,藉此執行適當的初始化。
含有支援欄位的屬性
您可以混合運算屬性的概念和私用欄位,然後建立快取的評估屬性。 例如,更新 FullName
屬性,以便在第一次存取時進行字串格式化:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public required string FirstName { get; init; }
public required string LastName { get; init; }
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}
此實作可運作,因為 FirstName
和 LastName
屬性為唯讀。 人員可以變更名稱。 如要更新 FirstName
和 LastName
屬性以允許 set
存取子,您必須讓 fullName
的任何快取值失效。 您可以修改 FirstName
和 LastName
屬性的 set
存取子,以便讓 fullName
欄位重新運算:
public class Person
{
private string? _firstName;
public string? FirstName
{
get => _firstName;
set
{
_firstName = value;
_fullName = null;
}
}
private string? _lastName;
public string? LastName
{
get => _lastName;
set
{
_lastName = value;
_fullName = null;
}
}
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}
這個最終版本只會在必要時評估 FullName
屬性。 如果先前計算的版本有效,系統會使用該版本。 否則,計算會更新快取值。 使用此類別的開發人員無需知道實作的細節。 這些內部變更不會影響 Person 物件的使用。
從 C# 13 開始,您可以在 partial
類別建立partial
屬性。 屬性的 partial
實作宣告不能是自動實作的屬性。 自動實作的屬性會使用與宣告部分屬性宣告相同的語法。
屬性
屬性是類別或物件中的一種智慧型欄位。 從物件外部來看,屬性就像是物件中的欄位。 不過,您可以使用完整的 C# 功能選擇區來實作屬性。 您可以提供驗證、不同的存取範圍、延遲評估,或是您的案例所需的任何需求。
- 不需要自定義存取子程式代碼的簡單屬性可以實作為表達式主體定義或 自動實作屬性。
- 屬性可讓類別公開取得和設定值的公用方式,同時隱藏實作或驗證程式碼。
- get 屬性存取子可用來傳回屬性值,而 set 屬性存取子則用來指派新值。 init 屬性存取子只會在物件建構期間用來指派新的值。 這些存取子可以有不同的存取層級。 如需詳細資訊,請參閱限制存取子的存取範圍。
- value 關鍵字是用來定義
set
或init
存取子所要指派的值。 - 屬性可以是「讀寫」(同時具有
get
和set
存取子)、「唯讀」(具有get
存取子但沒有set
存取子) 或「唯寫」(具有set
存取子但沒有get
存取子)。 唯寫的屬性很少見。
C# 語言規格
如需詳細資訊,請參閱 C# 語言規格的屬性。 語言規格是 C# 語法及用法的限定來源。