TOC

This article is currently in the process of being translated into Chinese (~97% done).

类 :

Properties

在之前的章節中,我們探討了什麼是欄位,它們就像是限定於一個類別裡的全域變數,你可以在那個類別的任何一個方法存取它。我們也提到了欄位其實可以被其他類別存取,前提是你把它設定為public,但是通常不推薦這麼做。如果你想做到這件事,你應該要使用屬性

當你把一個欄位設定為public時,其他的類別就可以任意的修改或讀取它,並且在這之前不會有任何通知,而你也沒辦法控制什麼樣的值可以或不可以賦予給它。屬性的特點是它可以在有人嘗試讀寫它時取得通知,並且可以在別人嘗試給它賦值之前檢查那個值,或者是在別人嘗試讀取它的時候控制別人讀出來的值,同時也可以決定自己是不是唯讀或「唯寫」。

屬性看起來像是欄位和方法的合體,因為它的宣告方式就像是欄位一樣有存取修飾詞、型別和名字,但是同時也有一個程式碼區塊用來控制它的行為,這就跟方法很像。

public string Name
{
	get { return _name; }
	set { _name = value; }
}

getset是專屬於屬性的關鍵字,它們是用來控制當別人嘗試讀取(get)和修改(set)它時應該要做什麼事。你可以宣告一個只有get或只有set的屬性,這會讓它變得唯讀或唯寫。get和set本身也可以有存取修飾詞,來控制它在什麼地方可以被讀寫,例如public get和private set的組合就會使得它可以在任何地方被讀取,但是只能在它屬於的那個類別裡被修改。

你會發現在這個例子中,我使用了一個叫做_name的欄位,這是你額外需要宣告的,所以你的屬性才可以使用它。你可以把屬性想像成是一個存取欄位的「門面」,真正存放著資料的東西是欄位而不是屬性,屬性的用途是控制讀寫行為而不是存放資料。順帶一提,很多初學者常犯的錯誤是嘗試把值直接存到屬性裡,但這會導致無限遞迴使得程式崩潰。

private string _name = "John Doe";

public string Name
{
	get { return _name; }
	set { _name = value; }
}

你可以發現欄位和屬性是相輔相成的,get負責在別人嘗試讀取它時提供給讀取的人值,在這個例子中,它把_name的值回傳給讀取者,而set負責在別人嘗試給予它值時檢查並儲存那個值,在這個例子中,它把別人嘗試給予它的值存在_name中。在set中,有一個關鍵字是value,它代表的是別人現在正要嘗試給它的值。

到目前為止,我們所做的事情都是一個public欄位可以做到的,但是在之後,你可能會想要在別人嘗試讀寫Name時做更多的控制,而這時你只要直接修改get和set裡的內容就好,而不會影響到其他使用這個類別的程式碼,如果你一開始先使用欄位,但是在發現你需要更多控制時才改為使用屬性,則其他使用你這個類別的程式碼都需要被重新編譯,所以倒不如一開始就直接使用屬性。以下是一個屬性控制讀寫的例子:

private string _name = "John Doe";

public string Name
{
	get 
	{
		return _name.ToUpper();
	}
	set 
	{
		if(!value.Contains(" "))
			throw new Exception("Please specify both first and last name!");
		_name = value; 
	}
}

現在當別人嘗試讀取Name時,get會使得讀取它的人拿到的值是全部大寫的,不管_name裡的值是不是全部大寫。而在別人嘗試給予Name值時,如果那個值沒有包含空格,則set會丟出一個例外(例外會在後面的章節介紹)。雖然這是一個很粗糙的例子,但是它可以讓你了解屬性可以做到的控制是什麼樣子。

唯讀屬性

在這個教學裡,你看到的屬性通常都是可讀可寫的,因為這是最常見的用法,但是這不是唯一可能的情況,你可以宣告一個只有get的屬性,例如:

private string _name = "John Doe";

public string Name
{
	get { return _name; }
}

在這個情況下,你就不能再修改Name的值了,如果你這麼做的話,會產生編譯錯誤,雖然如果你目前處於Name所處的類別裡,你還是可以直接修改_name(因為它是private),但是這麼做就失去了使用屬性的意義了,屬性的其中一個主要用途就是檢查它被給予的值,如果你直接繞過它把值給_name,那麼有可能會在你的程式逐漸複雜時產生意想不到的錯誤,因為你無從檢查_name的值。

幸運的是,C#允許你給予get和set存取修飾詞,如果你使用private set或protected set,你就可以在該類別內修改Name並且可以檢查它被賦予的值(如果你使用protected則衍生類別也可以修改),如下:

private string _name = "John Doe";

public string Name
{
	get { return _name; }

	private set
	{
		if(IsValidName(value))
			this._name = value;
	}
}

public bool IsValidName(string name)
{
	return name.EndsWith("Doe");

}

在這裡,set前面被加了private,這代表Name現在可以在它所處的類別中被修改,但是其他類別都不能修改它,你也可以把它改成protected或internal,而這些存取修飾詞的差別會在之後的章節中介紹。如果你不幫get和set加存取修飾詞,則它們預設會有與那個屬性一樣的存取修飾詞,在這個例子中Name的get沒有存取修飾詞,所以它預設是public,也就是Name本身的存取修飾詞。

自動實作屬性

在某些情況下,你不需要屬性有任何特殊的動作,只想要它可以像一個欄位一樣被讀寫而沒有任何的額外行為,而使得要寫一個屬性還要寫一個欄位顯得多餘,在這個情況下,千萬不要直接偷懶宣告一個public欄位,因為你可能會在未來改成屬性影響到其他使用這個類別的程式碼。為了減少麻煩,你可以在這情況下使用自動實作屬性:

手動使用欄位的寫法

private string _name;

public string Name
{
	get { return _name; }
	set { _name = value; }
}

自動實作屬性的寫法

public string Name { get; set; }

你可以發現我們可以不用再額外宣告一個private的欄位了,但是它其實還是存在,只是現在編譯器會自動幫你添加它,如果你在日後想要更多控制的話,你只要把自動實作屬性改回手動寫法就好,而不會影響其他使用這個類別的程式碼。

即使你使用了自動實作屬性,你還是可以做到我們剛剛提到的一些事,例如唯獨屬性,或是在get和set前面加上存取修飾子,例如private set等。

public string ReadOnlyProperty { get; }

但是你不可以使用自動實作屬性來達到唯寫(只有set)的效果。

有預設值的自動實作屬性

在C# 6以前,你必須要使用手動屬性才能給屬性預設值。

private string _name = "John Doe";

public string Name
{
	get { return _name; }
	set { _name = value; }
}

但是在那之後,你可以像下面這樣給自動實作屬性預設值:

public string Name { get; set; } = "John Doe";

運算式主體屬性

運算式主體在C# 6和C# 7中被加入,它可以讓你簡化只有一行程式碼的屬性或方法,如下:

private string name;
public string Name
{
	get => name;
	set => name = value;
}

如果你的屬性是唯讀的,就可以寫得更精簡:

public string Name => "John Doe";

當然你也可以在運算式主體中對資料做一些處理:

public string Name { get; set; } = "John Doe";

public string FirstName => this.Name.Substring(0, this.Name.IndexOf(" "));

這會讓你在存取FirstName時,先把Name以空格分開,再回傳第一個分段,其實這種寫法非常類似於方法,因為FirstName裡面本身沒有存放任何資料,而是在它被讀取的時候才及時計算出一個值。使用運算式主體可以讓你使用get而不用寫出getreturn,讓你的程式碼變得精簡。

總結

屬性可以讓你對一個欄位怎麼被讀寫有更多的控制,因此如果你要讓其他類別存取你的欄位,請務必使用它。


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!