如何在.NET中修改List <t>结构?</t>

时间:2010-07-07 14:25:11

标签: .net list

为什么我无法修改列表项?

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?只是为了作业而有点复杂?!。

3 个答案:

答案 0 :(得分:12)

因为Foo是一个结构而不是一个对象。因此,它是值类型而不是引用类型。

将值类型添加到List时,会生成一个副本(与列表中的条目是对原始对象的引用的引用类型不同)。

将实例拉出List也是如此。执行Foo tempFoo = foos[0]时,您实际上是在foos[0]制作该元素的另一个副本,因此您要修改副本而不是列表中的副本。

foos[0].Name = "fooNameTwo";

出于同样的原因给你一个错误。 List的索引器仍然是一个返回值的函数。由于您正在使用值类型,该函数将返回一个副本并将其存储在堆栈中供您使用。一旦您尝试修改该副本,编译器就会发现您正在做一些会产生意外结果的事情(您的更改不会反映在List中的元素中)和barfs。

正如Jon Skeet评论的那样......另一个原因是Mutable Structs是邪恶的(如果你想了解更多细节,请查看这个问题: Why are mutable structs evil? )。

如果你让Foo成为一个类而不是一个结构,你将得到你正在寻找的行为。

答案 1 :(得分:2)

这就是事情。

你这样说:

  

我想离开Foo的结构。   我该怎么办? foos [0] =   tempFoo?有点复杂   作业?!。

是的,嗯,就是这样。您需要的是作业,而不是修改。值类型(结构)通常应被视为

选择int。如果您有一个名为List<int>的{​​{1}},您会如何更改ints处的值?

这样的事,对吧?

ints[0]

请注意,没有办法做这样的事情:

ints[0] = 5; // assignment

那是因为ints[0].ChangeTo(5); // modification? 是一个不可变的结构。它被视为,无法更改(因此Int32变量只能分配给新的)。

您的int结构是一个令人困惑的案例,因为它可能会发生变异。但由于它是值类型,因此只传递副本(与Foointdouble等相同 - 我们定期处理的所有值类型) 。出于这个原因,你不能改变一个“远远地”的实例 - 也就是说,除非它被ref传递给你(在方法调用中使用DateTime关键字)。

所以简单的答案是,是的,要更改ref中的Foo,您需要将其分配给新的。但你真的不应该有一个可变的结构开始。

免责声明:与几乎所有可以收到的建议一样,在任何情况下,这都不是100%的规则。非常熟练的开发人员Rico MarianiPoint3d结构体写为可变的,有充分理由which he explained on his blog。但这是非常知识渊博的开发人员知道完全他正在做什么的情况;通常,作为编写值类型与引用类型的标准方法,值类型应该是不可变的。


回复你的评论:所以当你 处理像Point这样的可变结构时,基本上你需要做这样的事情:

List<Foo>

或者,或者:

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); 的过度调用(无理由再制作副本)。

答案 2 :(得分:0)

'因为Foo是值类型

而不是

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

这样做:

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