为什么我无法修改列表项?
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
?只是为了作业而有点复杂?!。
答案 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
结构是一个令人困惑的案例,因为它可能会发生变异。但由于它是值类型,因此只传递副本(与Foo
,int
,double
等相同 - 我们定期处理的所有值类型) 。出于这个原因,你不能改变一个“远远地”的实例 - 也就是说,除非它被ref传递给你(在方法调用中使用DateTime
关键字)。
所以简单的答案是,是的,要更改ref
中的Foo
,您需要将其分配给新的。但你真的不应该有一个可变的结构开始。
免责声明:与几乎所有可以收到的建议一样,在任何情况下,这都不是100%的规则。非常熟练的开发人员Rico Mariani将Point3d
结构体写为可变的,有充分理由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;