2011-04-13 104 views
52

我知道值類型應該是不可變的,但這只是一個建議,而不是一個規則,對嗎? 那麼,爲什麼我不能做這樣的事情:在C#中,爲什麼我不能在foreach循環中修改值類型實例的成員?

struct MyStruct 
{ 
    public string Name { get; set; } 
} 

public class Program 
{ 
    static void Main(string[] args) 
    { 
     MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } }; 
     foreach (var item in array) 
     { 
      item.Name = "3"; 
     } 
     //for (int i = 0; i < array.Length; i++) 
     //{ 
     // array[i].Name = "3"; 
     //} 

     Console.ReadLine(); 
    } 
} 

而評論for循環正常工作的代碼foreach循環不能編譯。錯誤消息:

不能修改「項目」的成員,因爲它是一個「的foreach迭代變量」

這是爲什麼?

+3

+1:好問題。我已經知道很長一段時間了'foreach'循環變量不能被修改,但我從來沒有真正學過**爲什麼**! – jp2code 2011-04-13 15:18:11

回答

54

因爲使用的foreach一個枚舉,和枚舉不能改變底層集合,但可以,然而,改變由的集合中的對象所引用的任何對象。這就是Value和Reference-type語義發揮作用的地方。

在引用類型上,即一個類,所有集合存儲的是對對象的引用。因此,它實際上不會觸及任何對象的成員,也不會在意它們。對象的更改不會觸及集合。

另一方面,值類型將其整個結構存儲在集合中。如果不更改集合並使枚舉器無效,則無法觸及其成員。

而且,枚舉器返回副本中的值。在ref-type中,這沒有任何意義。引用的副本將是相同的引用,您可以以任何想要的方式更改引用的對象,並將這些更改散佈在範圍之外。另一方面,在數值類型上,意味着你得到的只是對象的副本,因此對所述副本的任何更改都不會傳播。

+0

優秀的答案。哪裏可以找到更多關於「價值類型將其整個結構存儲在集合中」的證明? – CuiPengFei 2011-04-13 15:18:17

+0

@CuiPengFei:值類型將其整個值存儲在變量中。數組只是一塊變量。確切地說, – 2011-04-13 15:26:51

+0

。值類型將其整個結構放置在任何放置的容器中,例如集合,局部變量等。 順便說一句,這就是爲什麼最好使結構不可變:最終會得到大量「相同「的對象,很難跟蹤哪個人採取了變化,是否會傳播和在哪裏。你知道,字符串發生的典型問題。 – Kyte 2011-04-13 15:58:26

2

注意:根據亞當的評論,這實際上不是問題的正確答案/原因。儘管如此,仍然值得注意。

MSDN。在使用枚舉器時,您不能修改這些值,這基本上就是正在執行的操作。

枚舉數可用於讀取集合中的 數據,但 不能用來修改 底層集合。

只要 集合保持不變,枚舉數仍然有效。如果 對集合進行了更改,如 (如添加,修改或刪除元素),則枚舉數爲 將無法​​恢復,並且其 行爲未定義。

+1

雖然它可能看起來像這樣,但這不是問題中的問題。您當然可以修改通過'foreach'獲得的實例的屬性或字段值(這不是MSDN文章的討論內容),因爲這是修改集合的*成員*,而不是集合本身。這裏的問題是值類型與參考類型語義。 – 2011-04-13 14:21:18

+0

的確,我剛剛閱讀了你的答案,並以我的方式意識到了錯誤。我會留下它,因爲它仍然有點用處。 – Ian 2011-04-13 14:22:04

+0

謝謝,但我不認爲這是原因。因爲我試圖改變集合元素的成員之一,而不是集合本身。如果我將MyStruct從一個結構體改爲一個類,那麼foreach循環就會起作用。 – CuiPengFei 2011-04-13 14:24:41

14

這意味着沒有什麼能夠阻止你違反它,但它確實應該比「這是一個建議」更重要。例如,你在這裏看到的原因。

值類型將實際值存儲在變量中,而不是參考。這意味着您的數組中包含該值,並且您在item中擁有該值的副本,而不是引用。如果您允許更改item中的值,則它不會反映在數組中的值,因爲它是一個全新的值。這就是爲什麼它不被允許。

如果你想做到這一點,你必須通過循環指數在陣列上,而不是使用一個臨時變量。

+0

Downvoter care to comment? – 2011-04-13 14:31:38

+0

我沒有downvote,但我相信[你提到爲什麼foreach是隻讀的原因並不完全正確](http://stackoverflow.com/questions/4004755/why-is-foreach-loop-read-only-在-C/4004778#4004778)。 – 2011-04-13 14:37:43

+0

@Steven:我提到的理由是正確的,如果不是在相關答案中詳細描述的話。 – 2011-04-13 14:52:44

1

使MyStruct成爲一個類(而不是struct),你就可以做到這一點。

+0

這是正確的基於VT(值類型)與RT(參考類型),但沒有真正回答這個問題。 – JonH 2011-04-13 14:26:16

+0

你說得對。我錯過了這裏的觀點。 – 2011-04-13 14:27:38

+0

謝謝,我知道這會使它工作。但我只是想弄清楚原因。 – CuiPengFei 2011-04-13 14:31:10

1

數值類型爲called by value。簡而言之,當你評估變量時,它的一個副本。即使這是允許的,您將編輯一個副本,而不是原始實例MyStruct

foreach is readonly in C#。對於引用類型(類),這並沒有改變太多,因爲只有引用是隻讀的,所以你仍然可以做到以下幾點:

MyClass[] array = new MyClass[] { 
     new MyClass { Name = "1" }, 
     new MyClass { Name = "2" } 
    }; 
    foreach (var item in array) 
    { 
     item.Name = "3"; 
    } 

然而,對於值類型(結構),整個對象是隻讀,導致你正在經歷的事情。你不能在foreach中調整對象。

+0

謝謝,但我不認爲你的解釋是100%正確的。因爲如果我將ChangeName方法添加到MyStruct並在foreach循環中調用它,它將工作得很好。 – CuiPengFei 2011-04-13 14:47:31

+0

@CuiPengFei:它編譯,但如果你事後驗證原始實例,你會發現它們沒有改變。 (由於價值類型的'按價值調用'行爲。) – 2011-04-13 14:50:15

+0

哦,是的,對於不謹慎的評論感到抱歉。 – CuiPengFei 2011-04-13 14:56:44

8

結構是值類型。
類是引用類型。

ForEach結構使用的IEnumerator的IEnumerable的數據類型要素。當發生這種情況時,那麼變量是隻讀的,你不能修改它們,因爲你有值類型,所以你不能修改任何包含的值,因爲它共享相同的內存。

C#朗規範第8.8.4:

迭代變量相當於一個只讀的局部變量,即延伸到嵌入語句

爲了解決這個問題,採用A級範圍insted的結構;

class MyStruct { 
    public string Name { get; set; } 
} 

編輯:@CuiPengFei

如果使用var隱式類型,它很難讓編譯器來幫助你。如果你使用MyStruct它會告訴你在結構的情況下,它是隻讀的。在類的情況下,對項目的引用是隻讀的,所以你不能在循環內寫入item = null;,但你可以改變它的屬性,這是可變的。

您也可以使用(如果你喜歡用struct):

 MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } }; 
     for (int index=0; index < array.Length; index++) 
     { 
      var item = array[index]; 
      item.Name = "3"; 
     } 
+0

謝謝,但你怎麼解釋一個事實,如果我添加一個ChangeName方法到MyStruct並在foreach循環中調用它,它會工作得很好? – CuiPengFei 2011-04-13 14:50:29

+0

我最近的評論是錯誤的,對不起。 – CuiPengFei 2011-04-13 14:57:03

相關問題