C# 更改结构列表中元素的值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/51526/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 10:04:32  来源:igfitidea点击:

Changing the value of an element in a list of structs

提问by Darren

I have a list of structs and I want to change one element. For example :

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

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

Now I want to change one element:

现在我想改变一个元素:

MyList[1].Name = "bob"

However, whenever I try and do this I get the following error:

但是,每当我尝试这样做时,都会出现以下错误:

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

无法修改 System.Collections.Generic.List.this[int]' 的返回值,因为它不是变量

If I use a list of classes, the problem doesn't occur.

如果我使用类列表,则不会出现问题。

I guess the answer has to do with structs being a value type.

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

So, if I have a list of structs should I treat them as read-only? If I need to change elements in a list then I should use classes and not structs?

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

采纳答案by Andrew

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

structs in C# should almost always be designed to be immutable (that is, have no way to change their internal state once they have been created).

C# 中的结构应该几乎总是被设计为不可变的(也就是说,一旦它们被创建,就无法改变它们的内部状态)。

In your case, what you want to do is to replace the entire struct in specified array index, not to try to change just a single property or field.

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

回答by Gishu

Not quite. Designing a type as class or struct shouldn't be driven by your need to store it in collections :) You should look at the 'semantics' needed

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

The problem you're seeing is due to value type semantics. Each value type variable/reference is a new instance. When you say

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

Struct obItem = MyList[1];

what happens is that a new instance of the struct is created and all members are copied one by one. So that you have a clone of MyList[1] i.e. 2 instances. Now if you modify obItem, it doesn't affect the original.

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

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

Now bear with me for 2 mins here (This takes a while to gulp down.. it did for me :) If you really need structs to be stored in a collection and modified like you indicated in your question, you'll have to make your struct expose an interface (However this will result in boxing). You can then modify the actual struct via an interface reference, which refers to the boxed object.

现在在这里忍受我 2 分钟(这需要一段时间才能吞下......对我来说确实如此:)如果你真的需要将结构存储在一个集合中并像你在问题中指出的那样修改,你必须做您的结构公开了一个接口(但是这会导致装箱)。然后,您可以通过接口引用修改实际结构,该引用引用装箱对象。

The following code snippet illustrates what I just said above

下面的代码片段说明了我上面所说的

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. Good Question.
Update:@Hath - you had me running to check if I overlooked something that simple. (It would be inconsistent if setter properties dont and methods did - the .Net universe is still balanced :)
Setter method doesn't work
obList2[1] returns a copy whose state would be modified. Original struct in list stays unmodified. So Set-via-Interface seems to be only way to do it.

哈。好问题。
更新:@Hath - 你让我跑去检查我是否忽略了这么简单的事情。(如果 setter 属性和方法没有,这将是不一致的 - .Net 世界仍然是平衡的 :)
Setter 方法不起作用
obList2[1] 返回一个其状态将被修改的副本。列表中的原始结构保持不变。所以 Set-via-Interface 似乎是唯一的方法。

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);
}

回答by Jason Olson

It's not so much that structs are "immutable."

结构并不是“不可变的”。

The real underlying issue is that structs are a Value type, not a Reference type. So when you pull out a "reference" to the struct from the list, it is creating a new copy of the entire struct. So any changes you make on it are changing the copy, not the original version in the list.

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

Like Andrew states, you have to replace the entire struct. As that point though I think you have to ask yourself why you are using a struct in the first place (instead of a class). Make sure you aren't doing it around premature optimization concerns.

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

回答by supercat

There is nothing wrong with structs that have exposed fields, or that allow mutation via property setters. Structs which mutate themselves in response to methods or property getters, however, are dangerous because the system will allow methods or property getters to be called on temporary struct instances; if the methods or getters make changes to the struct, those changes will end up getting discarded.

具有公开字段或允许通过属性设置器进行更改的结构没有任何问题。然而,响应方法或属性获取器而改变自身的结构是危险的,因为系统将允许在临时结构实例上调用方法或属性获取器;如果方法或 getter 对结构进行了更改,这些更改最终将被丢弃。

Unfortunately, as you note, the collections built into .net are really feeble at exposing value-type objects contained therein. Your best bet is usually to do something like:

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

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

Somewhat annoying, and not at all threadsafe. Still an improvement over a List of a class type, where doing the same thing might require:

有点烦人,而且根本不是线程安全的。仍然是对类类型的 List 的改进,在那里做同样的事情可能需要:

  myList[1].Name = "Albert";

but it might also require:

但它可能还需要:

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

or maybe

或者可能

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

or maybe some other variation. One really wouldn't be able to know unless one examined myClass as well as the other code that put things in the list. It's entirely possible that one might not be able to know whether the first form is safe without examining code in assemblies to which one does not have access. By contrast, if Name is an exposed field of MyStruct, the method I gave for updating it will work, regardless of what else MyStruct contains, or regardless of what other things may have done with myList before the code executes or what they may expect to do with it after.

或者也许是其他一些变化。除非有人检查 myClass 以及将事物放入列表的其他代码,否则人们真的无法知道。如果不检查无法访问的程序集中的代码,则完全有可能无法知道第一种形式是否安全。相比之下,如果 Name 是 MyStruct 的一个公开字段,则我提供的更新它的方法将起作用,无论 MyStruct 还包含什么,或者无论在代码执行之前对 myList 做了什么其他事情,或者他们可能期望做什么之后再做。

回答by David Klempfner

In addition to the other answers, I thought it could be helpful to explain why the compiler complains.

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

When you call MyList[1].Name, unlike an array, the MyList[1]actually calls the indexer method behind the scenes.

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

Any time a method returns an instance of a struct, you're getting a copy of that struct (unless you use ref/out).

任何时候一个方法返回一个结构的实例,你都会得到一个该结构的副本(除非你使用 ref/out)。

So you're getting a copy and setting the Nameproperty on a copy, which is about to be discarded since the copy wasn't stored in a variable anywhere.

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

Thistutorial describes what's going on in more detail (including the generated CIL code).

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