MyList.Addnew MyStruct"peter";
现在我要更改一个元素:
1MyList1.Name ="bob"
但是,每当"/>

关于c#:更改结构列表中元素的值

关于c#:更改结构列表中元素的值

Changing the value of an element in a list of structs

我有一个结构列表,我想更改一个元素。例如:

1
2
MyList.Add(new MyStruct("john");
MyList.Add(new MyStruct("peter");

现在我要更改一个元素:

1
MyList[1].Name ="bob"

但是,每当我尝试执行此操作时,都会出现以下错误:

Cannot modify the return value of
System.Collections.Generic.List.this[int]‘ because it is not
a variable

如果我使用类列表,则不会发生此问题。

我想答案与结构是值类型有关。

因此,如果我有一个结构列表,我应该将它们视为只读吗?如果我需要更改列表中的元素,那么应该使用类而不是结构?


不完全是。将类型设计为类或结构不应由将其存储在集合中的需求驱动:)您应该查看所需的"语义"

您看到的问题是由于值类型语义所致。每个值类型变量/引用都是一个新实例。当你说

1
Struct obItem = MyList[1];

发生的事情是创建了该结构的新实例,并且所有成员都被一个一个地复制。这样您就可以克隆MyList [1],即2个实例。
现在,如果修改obItem,它不会影响原始对象。

1
obItem.Name ="Gishu";  // MyList[1].Name still remains"peter"

现在在这里和我一起忍受2分钟(这需要花一会儿时间才能吞下..对我有用:)
如果确实需要将结构存储在集合中并按照问题中的指示进行修改,则必须使结构公开一个接口(但是这将导致装箱)。然后,您可以通过引用盒装对象的接口引用来修改实际的结构。

以下代码段说明了我在上面刚刚说过的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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宇宙仍然是平衡的:)
设置器方法不起作用
obList2 [1]返回其状态将被修改的副本。列表中的原始结构保持不变。因此,"通过接口设置"似乎是唯一的方法。

1
2
3
4
5
6
7
8
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);
}

1
MyList[1] = new MyStruct("bob");

C#中的结构几乎应始终设计成不可变的(也就是说,一旦创建,便无法更改其内部状态)。

在您的情况下,您要做的是替换指定数组索引中的整个结构,而不是尝试仅更改单个属性或字段。


结构"不可变"不是很多。

真正的潜在问题是结构是值类型,而不是引用类型。因此,当您从列表中拉出对该结构的"引用"时,它将创建整个结构的新副本。因此,您对此所做的任何更改都将更改副本,而不是列表中的原始版本。

就像安德鲁指出的那样,您必须替换整个结构。尽管如此,我认为您必须问自己,为什么首先要使用结构(而不??是类)。确保您没有围绕过早的优化问题进行操作。


具有公开字段或允许通过属性设置器进行突变的结构没有问题。但是,响应于方法或属性获取器而发生变异的结构很危险,因为系统允许在临时结构实例上调用方法或属性获取器。如果方法或获取方法更改了结构,则这些更改最终将被丢弃。

不幸的是,正如您所注意到的,.net内置的集合在暴露其中包含的值类型对象方面确实微不足道。最好的选择通常是执行以下操作:

1
2
3
  MyStruct temp = myList[1];
  temp.Name ="Albert";
  myList[1] = temp;

有点令人讨厌,而且根本不是线程安全的。仍然是对类类型的列表的一种改进,在这种情况下,可能需要执行以下操作:

1
  myList[1].Name ="Albert";

,但可能还需要:

1
  myList[1] = myList[1].Withname("Albert");

或者也许

1
2
3
  myClass temp = (myClass)myList[1].Clone();
  temp.Name ="Albert";
  myList[1] = temp;

或其他一些变化。除非一个人检查了myClass以及将这些东西放到列表中的其他代码,否则一个人实际上是不会知道的。如果不检查无法访问的程序集中的代码,则很可能无法知道第一种形式是否安全。相比之下,如果Name是MyStruct的公开字段,那么我给出的用于更新它的方法将起作用,而不管MyStruct包含的内容是什么,也不管代码执行前myList可能执行的其他操作或期望的代码是什么之后再做。


除了其他答案,我认为解释编译器抱怨的原因可能会有所帮助。

当您调用MyList[1].Name时,与数组不同,MyList[1]实际上是在幕后调用indexer方法。

每当一个方法返回一个结构的实例时,您都将获得该结构的副本(除非您使用ref / out)。

因此,您将获得一个副本,并在副本上设置Name属性,由于该副本未存储在任何地方的变量中,因此该属性将被丢弃。

本教程更详细地描述了正在发生的事情(包括生成的CIL代码)。


推荐阅读