2010-07-07 61 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