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

屬性會合併欄位和方法的各個層面。 對物件的使用者而言,屬性會呈現為欄位,而存取屬性需要相同的語法。 對類別的實作者,屬性是一或兩個程式代碼區塊,代表 get 存取子和/或 或 set 存取 init 子。 讀取 屬性時會執行 存取子的程式代碼區塊get;當指派值時,會執行 或 init 存取子的程式代碼區塊set。 沒有 set 存取子的屬性會視為唯寫。 沒有 get 存取子的屬性則視為唯寫。 具有這兩個存取子的屬性是讀寫。 您可以使用 存取 init 子,而不是 存取子,讓屬性設定為物件初始化的一 set 部分,否則使其成為只讀的。

與欄位不同,屬性不會分類為變數。 因此,您無法將屬性當做 refout 參數傳遞。

屬性有許多用途:

  • 他們可以在允許變更之前先驗證數據。
  • 它們可以在類別上以透明方式公開數據,其中該數據是從某些其他來源擷取,例如資料庫。
  • 當數據變更時,他們可以採取動作,例如引發事件,或變更其他欄位的值。

在類別區塊中宣告屬性的方式是指定欄位存取層級,其後依序接著屬性類型、屬性名稱,以及宣告 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 屬性使用私用欄位來追蹤實際值。 屬性資料的實際位置通常稱為屬性的「備份存放區」。屬性經常使用私人欄位作為備份存放區。 此欄位標示為私用,以確認只有呼叫屬性才能進行變更。 如需公用和私用存取限制的詳細資訊,請參閱存取修飾詞。 自動實作屬性提供簡單屬性宣告的簡化語法。 如需詳細資訊,請參閱自動實作的屬性

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子必須是表達式主體成員,或以傳回throw 語句結尾,且控件無法流離存取子主體。

警告

這是使用 get 存取子變更物件狀態的不良程式設計樣式。

get 存取子可用來傳回欄位值或計算它,並傳回它。 例如:

class Manager
{
    private string _name;
    public string Name => _name != null ? _name : "NA";
}

在上一個範例中,如果您未將值指派給 Name 屬性,則會傳回 值 NA

Set 存取子

set 存取子與傳回型別為 void 的方法類似。 它使用稱為 value 的隱含參數,其類型為屬性的類型。 編譯程式和 JIT 編譯程式也會辨識 或 init 存取子的set常見模式。 這些常見的模式已經過優化,直接寫入支援字段的記憶體。 在下列範例中,將 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 存取子只能在建構函式中使用,或透過物件初始設定式使用。

備註

屬性可標記為 publicprivateprotectedinternalprotected internalprivate protected。 這些存取修飾詞定義類別使用者如何存取屬性。 get相同屬性的和 set 存取子可以有不同的存取修飾詞。 例如, get 可能 public 允許從型別外部存取唯讀,而 set 可以是 privateprotected。 如需詳細資訊,請參閱存取修飾詞

屬性可以使用 關鍵詞宣告為靜態屬性 static 。 靜態屬性隨時可供呼叫端使用,即使沒有任何類別執行個體也是一樣。 如需詳細資訊,請參閱靜態類別和靜態類別成員

屬性可以使用 virtual 關鍵詞標示為虛擬屬性。 虛擬屬性可讓衍生類別使用 override 關鍵字覆寫屬性行為。 如需這些選項的詳細資訊,請參閱繼承

覆寫虛擬屬性的屬性也可為 sealed,指定它對衍生類別而言不再是虛擬。 最後,屬性可以宣告為 abstract。 抽象屬性不會在類別中定義實作,且衍生類別必須撰寫自己的實作。 如需這些選項的詳細資訊,請參閱抽象和密封類別以及類別成員

注意

static 屬性的存取子上使用 virtualabstractoverride 修飾詞是錯誤的。

範例

此範例示範執行個體、靜態和唯讀屬性。 它會從鍵盤接受員工姓名、將 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 修飾詞

覆寫屬性範例

在此範例中,CubeSquare 這兩個類別會實作抽象類別 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
*/

另請參閱