分享方式:


屬性 (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 更嚴格。
  • 自動實作的屬性可以宣告 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 修飾詞搭配使用,藉此執行適當的初始化。

必要屬性

前面的範例允許呼叫端使用預設建構函式建立 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# 語法及用法的限定來源。

另請參閱