2011-05-19 79 views
50

這是一種聲明不可變結構的正確方法嗎?使用公共只讀字段爲不可變結構工作嗎?

public struct Pair 
{ 
    public readonly int x; 
    public readonly int y; 

    // Constructor and stuff 
} 

我想不出爲什麼會遇到問題,但我只是想問一下確認。

在這個例子中,我使用了整數。如果我使用了一個類,但是這個類也是不可變的,像這樣?這應該也可以,對嗎?

public struct Pair 
{ 
    public readonly (immutableClass) x; 
    public readonly (immutableClass) y; 

    // Constructor and stuff 
} 

(旁白:據我所知,使用性能更加普及,並允許改變,但這種結構的目的是從字面上只存儲兩個值我在永恆的問題只是有興趣在這裏。)

+0

'readonly'性能/成員只能從構造函數(最晚)內。它們不能使用屬性初始化語法進行設置。 – 2011-05-19 18:32:09

+0

你可能想檢查[不可變類型:瞭解他們的好處並使用它們](http://codebetter.com/patricksmacchia/2008/01/13/immutable-types-understand-them-and-use-them/) – YetAnotherUser 2011-05-19 18:34:56

+0

'readonly'隻影響賦值操作符。它沒有C++的const關鍵字那樣強大的語義。 – 2011-05-19 18:47:52

回答

102

如果您打算使用結構,最好讓它們不可變。

使所有字段爲只讀是一種很好的方式來幫助(1)記錄結構是不可變的,並且(2)防止意外突變。

但是,有一個皺紋,實際上我正在計劃在下週的博客上發生奇怪的巧合。即:只讀結構字段是一個謊言。人們期望只讀字段不能改變,但當然可以。在結構字段上的「只讀」是在其帳戶中沒有金錢的聲明書寫檢查。 一個結構體不擁有它的存儲空間,它就是可以變異的存儲空間。

例如,讓我們把你的結構:

public struct Pair 
{ 
    public readonly int x; 
    public readonly int y; 
    public Pair(int x, int y) 
    { 
     this.x = x; 
     this.y = y; 
    } 
    public void M(ref Pair p) 
    { 
     int oldX = x; 
     int oldY = y; 
     // Something happens here 
     Debug.Assert(x == oldX); 
     Debug.Assert(y == oldY); 
    } 
} 

有什麼事情可以在「有事這裏」導致被侵犯調試斷言發生的呢?當然。

public void M(ref Pair p) 
    { 
     int oldX = this.x; 
     int oldY = this.y; 
     p = new Pair(0, 0); 
     Debug.Assert(this.x == oldX); 
     Debug.Assert(this.y == oldY); 
    } 
... 
    Pair myPair = new Pair(10, 20); 
    myPair.M(ref myPair); 

現在會發生什麼?斷言被違反! 「this」和「p」指的是相同的存儲位置。存儲位置發生了變化,所以「this」的內容會發生變化,因爲它們是相同的東西。該結構不能執行x和y的只讀性,因爲該結構不擁有存儲;存儲是一個局部變量,可以隨意變化。

你不能依賴關於一個結構中的只讀字段永遠不會被改變的不變性;唯一可以依賴的是你不能編寫直接改變它的代碼。但是通過這樣一個小鬼的工作,你可以間接地改變你想要的一切。

另見本發行喬·達菲的優秀博客文章:

http://joeduffyblog.com/2010/07/01/when-is-a-readonly-field-not-readonly/

+16

令人驚歎的是,你演示的許多邊緣案例讓我想到,「誰會做*那*?」 – 2011-05-19 19:15:24

+3

當我調試別人的代碼時,我發現你可以用'StructLayout.Explicit'做同樣的事情。 – 2011-05-19 19:51:53

+1

@Jim:正確;您可以將只讀字段和讀寫字段疊加到相同的存儲中,沒有任何問題。這很奇怪,但它是合法的。 – 2011-05-19 20:12:28

5

這將使它確實無法改變。我想你最好添加一個構造函數。
如果它的所有成員都是不可變的,這將使它完全不可變。這些可以是類或簡單的值。

+0

當然,我只是爲了簡單而離開它。 – Mike 2011-05-19 18:33:41

+0

那麼如果他們是不可變的類而不是整數?應該也可以嗎? – Mike 2011-05-19 18:35:37

+0

B ^)構造函數會很有幫助。除非你總是希望x和y爲零。 – 2011-05-19 18:35:45

1

編譯器將禁止分配給readonly字段以及只讀屬性。

我推薦使用只讀屬性主要是爲了公共接口的原因和數據綁定(這在字段上不起作用)。如果這是我的項目,我會要求,如果結構/類是公共的。如果它將在一個程序集內部或對一個類是私有的,我可以首先忽略它,然後將它們重構爲只讀屬性。

+2

編譯器能夠在編譯時檢測對'readonly'字段的賦值,並且如果檢測到錯誤就會引發編譯器錯誤。 – dtb 2011-05-19 18:38:44

+0

很高興知道。編輯我的答案。 – 2011-05-19 19:12:07

+0

運行時也會強制執行'readonly'語義。也就是說,如果'x'是'MyStruct'類型的'readonly'字段,'s'是'MyStruct'的一個實例,那麼'dynamic d = s; d.x = 42;'會拋出異常。 – 2011-05-19 20:36:26

相關問題