题
我有一个结构列表,我想更改一个元素。例如 :
MyList.Add(new MyStruct("john");
MyList.Add(new MyStruct("peter");
现在我想改变一个元素:
MyList[1].Name = "bob"
但是,每当我尝试这样做时,我都会收到以下错误:
无法修改system.collections.generic.list.this [int]'的返回值,因为它不是变量
如果我使用类列表,则不会出现问题。
我想答案与结构作为值类型有关。
所以,如果我有一个结构列表,我应该将它们视为 只读?如果我需要更改列表中的元素,那么我应该使用类而不是结构?
解决方案
MyList[1] = new MyStruct("bob");
C# 中的结构几乎应该始终设计为不可变的(也就是说,一旦创建就无法更改其内部状态)。
在您的情况下,您想要做的是替换指定数组索引中的整个结构,而不是尝试仅更改单个属性或字段。
其他提示
不完全的。将类型设计为类或结构不应由将其存储在集合中的需要驱动:)您应该查看所需的“语义”
您看到的问题是由于值类型语义造成的。每个值类型变量/引用都是一个新实例。当你说
Struct obItem = MyList[1];
所发生的情况是,创建了该结构的一个新实例,并且所有成员都被一一复制。这样你就有了 MyList[1] 的克隆,即2 个实例。现在如果你修改obItem,它不会影响原来的。
obItem.Name = "Gishu"; // MyList[1].Name still remains "peter"
现在请耐心等待我 2 分钟(这需要一段时间才能消化……它对我确实做到了:)如果您确实需要结构来存储在集合中并像您在问题中指出的那样修改,则必须使结构曝光一个接口(然而这会导致拳击)。然后,您可以通过接口引用(引用装箱对象)修改实际结构。
下面的代码片段说明了我上面所说的
public interface IMyStructModifier
{
String Name { set; }
}
public struct MyStruct : IMyStructModifier ...
List<Object> obList = new List<object>();
obList.Add(new MyStruct("ABC"));
obList.Add(new MyStruct("DEF"));
MyStruct temp = (MyStruct)obList[1];
temp.Name = "Gishu";
foreach (MyStruct s in obList) // => "ABC", "DEF"
{
Console.WriteLine(s.Name);
}
IMyStructModifier temp2 = obList[1] as IMyStructModifier;
temp2.Name = "Now Gishu";
foreach (MyStruct s in obList) // => "ABC", "Now Gishu"
{
Console.WriteLine(s.Name);
}
HTH。好问题。
更新: @Hath - 你让我跑来检查我是否忽略了那么简单的事情。(如果 setter 属性不这样做而方法这样做的话,这将是不一致的 - .Net 宇宙仍然是平衡的:)
Setter方法不起作用
obList2[1] 返回一个其状态将被修改的副本。列表中的原始结构保持不变。所以通过接口设置似乎是唯一的方法。
List<MyStruct> obList2 = new List<MyStruct>();
obList2.Add(new MyStruct("ABC"));
obList2.Add(new MyStruct("DEF"));
obList2[1].SetName("WTH");
foreach (MyStruct s in obList2) // => "ABC", "DEF"
{
Console.WriteLine(s.Name);
}
结构并不是“不可变的”。
真正的根本问题是结构是值类型,而不是引用类型。因此,当您从列表中提取对该结构的“引用”时,它会创建整个结构的新副本。因此,您对其所做的任何更改都会更改副本,而不是列表中的原始版本。
就像安德鲁所说,你必须替换整个结构。就这一点而言,我认为您必须问自己为什么首先使用结构体(而不是类)。确保您这样做不是为了过早优化问题。
具有公开字段或允许通过属性设置器进行突变的结构没有任何问题。然而,响应方法或属性获取器而改变自身的结构是危险的,因为系统将允许在临时结构实例上调用方法或属性获取器;如果方法或 getter 对结构进行更改,这些更改最终将被丢弃。
不幸的是,正如您所注意到的,.net 中内置的集合在公开其中包含的值类型对象方面确实很弱。你最好的选择通常是做类似的事情:
MyStruct temp = myList[1]; temp.Name = "Albert"; myList[1] = temp;
有点烦人,而且根本不是线程安全的。仍然是对类类型列表的改进,在列表中执行相同的操作可能需要:
myList[1].Name = "Albert";
但它可能还需要:
myList[1] = myList[1].Withname("Albert");
或者可能
myClass temp = (myClass)myList[1].Clone(); temp.Name = "Albert"; myList[1] = temp;
或者也许是其他一些变化。除非检查 myClass 以及将内容放入列表中的其他代码,否则人们真的无法知道。如果不检查无权访问的程序集中的代码,完全有可能无法知道第一种形式是否安全。相比之下,如果 Name 是 MyStruct 的公开字段,则我提供的用于更新它的方法将起作用,无论 MyStruct 还包含什么,或者无论代码执行之前对 myList 做了什么其他操作,或者它们可能期望做什么之后再做。