共用方式為


屬性 (C# 程式設計手冊)

屬性是一種提供彈性機制來讀取、寫入或運算資料欄位值的成員。 屬性會顯示為公用資料成員,但它們會以名為存取子的特殊方法來實作。 此功能可讓呼叫端容易存取資料,同時有助於提升資料的安全性和彈性。 屬性的語法是欄位的自然延伸。 欄位可定義儲存位置:

public class Person
{
    public string? FirstName;

    // Omitted for brevity.
}

自動實作屬性

屬性定義包含 getset 存取子的宣告,以擷取和指派該屬性的值:

public class Person
{
    public string? FirstName { get; set; }

    // Omitted for brevity.
}

前述範例所示為自動實作屬性。 編譯器會產生屬性的隱藏支援欄位。 編譯器也會實作 getset 存取子的主體。 任何屬性都會套用至自動實作的屬性。 您可以對屬性指定 field: 標籤,藉此將屬性套用至編譯器產生的支援欄位。

您可以將屬性初始化為預設值以外的值,方法是在屬性的右大括弧後方設定值。 您可能偏好將 FirstName 屬性的初始值設為空字串,而非 null。 您會如下列程式碼所示指定該值:

public class Person
{
    public string FirstName { get; set; } = string.Empty;

    // Omitted for brevity.
}

存取控制

上述範例所示為讀取/寫入屬性。 您也可以建立唯讀屬性,或是為 set 和 get 存取子提供不同的存取範圍。 假設您的 Person 類別只能允許從該類別中的其他方法變更 FirstName 屬性的值。 您可以為 set 存取子提供 private 存取範圍,而不是 public

public class Person
{
    public string? FirstName { get; private set; }

    // Omitted for brevity.
}

FirstName 屬性可從任何程式碼讀取,但只能從 Person 類別的程式碼指派。

您可以將任何嚴格的存取修飾詞新增至 set 或 get 存取子。 個別存取子的存取修飾符必須比屬性的存取權更嚴格。 由於 FirstName 屬性為 public,但 set 存取子為 private,因此上述程式碼合法。 您無法使用 public 存取子宣告 private 屬性。 屬性宣告也可以宣告為 protectedinternalprotected internal,甚至是 private

set 存取子有兩個特殊存取修飾詞:

  • set 存取子可將 init 當作存取修飾詞。 該 set 存取子只能從物件初始設定式或型別的建構函式呼叫。 它比 set 存取子的 private 更嚴格。
  • 自動實作的屬性可以宣告沒有 set 存取子的 get 存取子。 在此情況下,編譯器只允許從型別的建構函式呼叫 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 修飾詞搭配使用,藉此執行適當的初始化。

必要屬性

前面的範例允許呼叫端使用預設建構函式建立 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 屬性設定為 nulldefault。 如果類型不可為 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 沒有支援欄位。 該屬性每次都會運算。

含有支援欄位的屬性

您可以混合運算屬性的概念和私用欄位,然後建立快取的評估屬性。 例如,更新 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;
        }
    }
}

此實作可運作,因為 FirstNameLastName 屬性為唯讀。 人員可以變更名稱。 如要更新 FirstNameLastName 屬性以允許 set 存取子,您必須讓 fullName 的任何快取值失效。 您可以修改 FirstNameLastName 屬性的 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 關鍵字是用來定義 setinit 存取子所要指派的值。
  • 屬性可以是「讀寫」(同時具有 getset 存取子)、「唯讀」(具有 get 存取子但沒有 set 存取子) 或「唯寫」(具有 set 存取子但沒有 get 存取子)。 唯寫的屬性很少見。

C# 語言規格

如需詳細資訊,請參閱 C# 語言規格屬性。 語言規格是 C# 語法及用法的限定來源。

另請參閱