屬性(property)是一種用於訪問對象或類的內容的機制。可以像使用公共數據成員一樣使用屬性,但實際上屬性是稱為「訪問器」的一種特殊方法。

屬性(property)是一種"高級欄位」,它可能帶有一個 getter 和一個 setter,它們保護屬性的值,使之不會被外部胡亂篡改。

和欄位相比,屬性實現了對成員的封裝。

無參屬性

編輯
class A
{
    private int c;
    public int getC()
    {
        return c;
    }
    public void setC(int value)
    {
        c = value;
    }
}

在這裡的私有欄位稱為支持欄位(Backing Field)。

不過,這樣做有兩個明顯缺點,一是必須手打這些代碼;二是在訪問屬性時,必須調用方法,而不能直接使用點號加屬性名。

CLR 提供了稱為屬性的機制,解決了這兩個缺點。下面的寫法是經過簡化了的寫法:

private int c { get;  set;  }

這樣創建的屬性叫做自動實現的屬性。可以直接通過 A.c 訪問屬性,而非使用 A.getC 和 A.setC 方法了。

實際上,無參屬性僅僅是語法糖。通過編譯之後使用 iladsm 查看,我們可以發現,編譯器自動為我們生成了 get_c 和 sct_c 方法,以及一個支持欄位k_BackingField。

只讀和只寫屬性

編輯

可以通過將 get 或 set 設置為 private 獲得只讀和只寫屬性。

例如,如果 set 是私有的,則屬性就是只讀的。不過,屬性的值仍然可以被類型內部的成員修改:

public int c { get; private set; }

從 C# 6 開始,允許省略 set 獲得真正具有不變性的屬性:

public int c { get; }

此時這個欄位就真正地具有了不變性,當你初始化了這個欄位的值之後,就再也無法更改它的值。

帶有邏輯的屬性

編輯

通過為屬性的 get 和 set 中加入代碼,可以控制屬性的取值範圍。例如,要實現一個永遠為非負整數的屬性:

private int age;
public int Age
{
    get
    {
        return age;
    }
    set
    {
        if (value < 0)
        {
            throw new ArgumentOutOfRangeException("Age", value, "Age必须大于等于0");
        }
        age = value;
    }
}

在這段代碼中,關鍵字 value 代表賦值時傳入的新值。

有參屬性

編輯

有參屬性的 get 方法支持傳入一個或更多參數,set 方法支持傳入兩個或更多參數。

C# 中的有參屬性又叫索引器,顧名思義,它是重載[]操作符的一種方式。

雖然有參屬性(索引器)很少被使用,不過,它有一個常用場景是這樣的:一個類的成員包括一個集合。比如,記錄每天 24 小時溫度的 DayTemperature 類,具有一個長度為 24 的 double 集合成員 Temperatures。當拿到這個類的一個實例 d 時,訪問它的成員需要 d.Temperatures[6](代表早上 6 點的溫度)。如果對這個類實現有參屬性,可以直接使用 d[6] 獲得相同的值,省去了集合成員名稱 Temperatures。這種表示法不僅簡化了客戶端應用程式的語法,還使其他開發人員能夠更加直觀地理解類及其用途。

索引器至少要定義一個訪問方法(即 get 或 set)。

class Program
{
    static void Main(string[] args)
    {
        var d = new DayTemperature();
        d.temperature[0] = 20.5;
        d.temperature[1] = 22;
        //使用类索引器访问
        Console.WriteLine(d[1]);        //22
        Console.ReadKey();
    }
}
class DayTemperature
{
    public double[] temperature = new double[24];
    //类的索引器
    public double this[int index]
    {
        get
        {
            //检查索引范围
            if (index < 0 || index >= temperature.Length)
            {
                return -1;
            }
            else
            {
                return temperature[index];
            }
        }
        set
        {
            if (!(index < 0 || index >= temperature.Length))
            {
                temperature[index] = value;
            }
        }
    }
}

屬性的意義

編輯

通過屬性的封裝,保留了它與外部交互的能力,又實現了一種可靠的讀寫機制。私有的欄位、屬性和方法保護了這些成員,使它們不會被外界調用,因為這是外界無需知道的信息。

通過封裝,類型只需要向外部提供它應該知道的信息,否則就會出現「你知道的太多了」這種情形。