2010-07-07 62 views
4

爲什麼我無法修改列表項目?如何修改.NET中的結構列表<T>?

struct Foo 
{ 
    public string Name; 
} 

Foo foo = new Foo(); 
foo.Name = "fooNameOne"; 

List<Foo> foos = new List<Foo>(); 
foos.Add(foo); 

// Cannot modify the return value of 
// 'List<Foo>.this[int]' because it is not a variable 
//foos[0].Name = "fooNameTwo"; 

Foo tempFoo = foos[0]; 
tempFoo.Name = "fooNameTwo"; 

Console.WriteLine(foos[0].Name); // fooNameOne 

編輯
我要離開Foo的結構。我應該如何繼續? foos[0] = tempFoo?有點複雜只是爲了一項任務?!

+7

從根本上說,這是可變結構變爲邪惡的另一種情況。拒絕吧。 – 2010-07-07 14:36:36

回答

2

事情是這樣的。

你這樣說:

我要離開Foo的結構。 我應該如何繼續? foos [0] = tempFoo?有點複雜,只是 作業?!。

是的,就是這樣。你需要的是賦值,而不是修改。值類型(結構)通常應被視爲

int。如果您有一個List<int>,名爲ints,您將如何更改ints[0]的值?

就是這樣的吧?

ints[0] = 5; // assignment 

注意有沒有辦法做這樣的事情:

ints[0].ChangeTo(5); // modification? 

這是因爲Int32是一個不變的結構。它的目的是作爲,它不能被改變(所以int變量只能被分配給新的東西)。

您的Foo結構是一個令人困惑的案例,因爲它可以被突變。但由於它是一種數值類型,因此只能傳遞副本(與int,double,等相同 - 我們定期處理的所有值類型)。由於這個原因,你不能改變「從遠處」的實例 - 也就是說,除非它通過ref傳遞給你(在方法調用中使用ref關鍵字)。

所以簡單的答案是,是的,在List<Foo>中更改Foo,您需要將其分配給新的東西。但你真的不應該有一個可變的結構開始。

免責聲明:幾乎可以接受任何建議,在任何情況下,這都不是100%的硬性規定。非常熟練的開發人員Rico Mariani寫了Point3d結構,因爲很好的原因可變,which he explained on his blog。但這是一個非常知情的開發人員知道完全是他在做什麼;一般來說,作爲編寫值類型與引用類型的標準方法,值類型應該是不可變的。


在回答您的評論:所以當你處理像Point一個可變的結構,基本上你需要做這樣的事情:

Point p = points[0]; 
p.Offset(0, 5); 
points[0] = p; 

,或者:

Point p = points[0]; 
points[0] = new Point(p.X, p.Y + 5); 

原因我不會做...

points[0] = new Point(points[0].X, points[0].Y + 5); 

...是,在這裏你在points[0]兩次複製的價值。請記住,按索引訪問this屬性基本上是一個方法調用。這樣的代碼確實是這樣做的:

points.set_Item(0, new Point(points.get_Item(0).X, points.get_Item(0).Y + 5); 

注意過度調用get_Item(有一次額外的拷貝沒有很好的理由)。

+0

所以,如果我的Foo應該是一個點我需要做'points [0] = new Point(points [0] .X,points [0] .Y + 5)'?.. – serhio 2010-07-07 15:05:21

+2

@ serhio:我其實不會那麼做,因爲每次你調用'points [0]'你都會得到一個新的副本,我會這樣做:'Point p = points [0]; p.Offset(0,5 ); points [0] = p;' – 2010-07-07 15:12:04

+0

我相信編譯器應該優化'points [0] .X,points [0] .Y'只調用一次函數 – serhio 2010-07-07 15:22:41

12

因爲Foo是一個結構體而不是對象。因此,它是一種值類型而不是引用類型。

將值類型添加到列表時,會創建副本(與列表中的條目是對原始對象的引用的引用類型不同)。

從列表中拉出實例也是一樣。當你做Foo tempFoo = foos[0]時,你實際上正在製作foos[0]元素的另一個副本,所以你要修改副本而不是列表中的副本。

foos[0].Name = "fooNameTwo"; 

由於相同的原因,給你一個錯誤。 List的索引器仍然是一個返回值的函數。由於您使用的是值類型,因此該函數正在返回副本並將其存儲在堆棧中供您使用。只要您嘗試修改該副本,編譯器就會發現您正在做的事情會產生意想不到的結果(您的更改不會反映在List中的元素中)和barfs。

正如Jon Skeet所評論的...只是另一個原因Mutable Structs是邪惡的(如果你想了解更多細節,請看看這個SO問題:Why are mutable structs evil?)。

如果你讓Foo成爲一個類而不是一個結構體,你會得到你正在尋找的行爲。

+1

(Make Foo一個班,它應該可以工作) – 2010-07-07 14:28:06

+0

錯誤'不能修改'列表 .this [int]'的錯誤,因爲它不是變量' – serhio 2010-07-07 14:31:51

+1

@serhio - 同樣的原因。名錄上的索引器是一個真正的功能。該函數返回(因爲它不是引用類型)值的副本並將其存儲在堆棧中。當您嘗試修改它時,您仍然在修改副本...和編譯器barfs。點擊這裏查看更多詳情:http://generally.wordpress.com/2007/06/21/c-list-of-struct/ – 2010-07-07 14:35:50

0

「導致富是值類型

,而不是這個

Foo tempFoo = foos[0]; 
tempFoo.Name = "fooNameTwo"; 

做到這一點:

Foo tempFoo; 
tempFoo = "fooNameTwo"; 
foos[0] = tempFoo; 
+0

哦啦...只是爲了設置名稱的3行代碼? :( – serhio 2010-07-07 14:35:54

+0

@serhio:使用'''而不是'struct'。.NET中的'struct'幾乎從來不是你真正想要的,它與C/C++結構不同。 – 2010-07-07 14:46:34

+0

@ 0xA3你沒看過編輯... – serhio 2010-07-07 15:06:30