C# 直接修改 List<T> 元素

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/414981/
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-04 02:31:44  来源:igfitidea点击:

Directly modifying List<T> elements

c#.net

提问by Joan Venge

I have this struct:

我有这个结构:

struct Map
{
    public int Size;

    public Map ( int size )
    {
        this.Size = size;
    }

    public override string ToString ( )
    {
        return String.Format ( "Size: {0}", this.Size );
    }
}

When using array, it works:

使用数组时,它的工作原理:

Map [ ] arr = new Map [ 4 ] {
    new Map(10),
    new Map(20),
    new Map(30),
    new Map(40)};

arr [ 2 ].Size = 0;

But when using List, it doesn't compile:

但是当使用 List 时,它不会编译:

List<Map> list = new List<Map> ( ) {
    new Map(10),
    new Map(20),
    new Map(30),
    new Map(40)};

list [ 2 ].Size = 0;

Why?

为什么?

采纳答案by Dirk Vollmar

The C# compiler will give you the following error:

C# 编译器会给你以下错误:

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

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

The reason is that structs are value types so when you access a list element you will in fact access an intermediate copy of the element which has been returned by the indexer of the list.

原因是结构是值类型,因此当您访问列表元素时,您实际上将访问该元素的中间副本,该副本已由列表的索引器返回。

From MSDN:

MSDN

Error Message

Cannot modify the return value of 'expression' because it is not a variable

An attempt was made to modify a value type that was the result of an intermediate expression. Because the value is not persisted, the value will be unchanged.

To resolve this error, store the result of the expression in an intermediate value, or use a reference type for the intermediate expression.

错误信息

无法修改“表达式”的返回值,因为它不是变量

试图修改作为中间表达式结果的值类型。因为该值不是持久化的,所以该值将保持不变。

要解决此错误,请将表达式的结果存储在中间值中,或对中间表达式使用引用类型。

Solutions:

解决方案:

  1. Use an array. This gives you direct access to the elements (you are not accessing a copy)
  2. When you make Map a class you can still use a List to store your element. You will then get a reference to a Map object instead of an intermediate copy and you will be able to modify the object.
  3. If you cannot change Map from struct to a class you must save the list item in a temporary variable:
  1. 使用数组。这使您可以直接访问元素(您不是访问副本)
  2. 当您将 Map 设为类时,您仍然可以使用 List 来存储您的元素。然后,您将获得对 Map 对象的引用而不是中间副本,并且您将能够修改该对象。
  3. 如果您无法将 Map 从结构更改为类,则必须将列表项保存在临时变量中:

 

 

List<Map> list = new List<Map>() { 
    new Map(10), 
    new Map(20), 
    new Map(30), 
    new Map(40)
};

Map map = list[2];
map.Size = 42;
list[2] = map;

回答by GvS

Because it is a struct, when using the List<T>, you're are creating copies.

因为它是struct,所以在使用 List<T> 时,您正在创建副本。

When using a struct, it is better to make them immutable. This will avoids effects like this.

使用结构时,最好使它们不可变。这将避免这样的影响。

When using an array, you have direct access to the memory structures. Using the List<T>.get_Item you work on a return value, that is, a copy of the structure.

使用数组时,您可以直接访问内存结构。使用 List<T>.get_Item 处理返回值,即结构的副本。

If it was a class, you would get a copy of a pointer to the class, but you would not notice this, since pointers are hidden in C#.

如果它是一个类,您将获得指向该类的指针的副本,但您不会注意到这一点,因为指针隐藏在 C# 中。

Also using the List<T>.ToArray does not solve it, because it will create a copy of the internal array, and return this.

同样使用 List<T>.ToArray 并不能解决它,因为它会创建内部数组的副本,并返回它。

And this is not the only place where you will get effects like this when using a struct.

并且这不是使用结构体时您将获得此类效果的唯一地方。

The solution provided by Divo is a very good workaround. But you have to remember to work this way, not only when using the List<T> but everywhere you want to change a field inside the struct.

Divo 提供的解决方案是一个很好的解决方法。但是您必须记住以这种方式工作,不仅在使用 List<T> 时,而且在您想要更改结构内的字段的任何地方。

回答by Micha? Ziober

list [ 2 ].Size = 0;

is in fact:

实际上是:

//Copy of object
Map map = list[2];
map.Size = 2;

Use classin place of struct.

使用class到位struct

回答by Mike Rosenblum

I'm not an XNA developer, so I cannot comment on how strict is the requirement to use structs vs. classes for XNA. But this does seem to be a much more natural place to use a class in order to get the pass-by-ref semantics you are looking for.

我不是 XNA 开发人员,所以我无法评论对 XNA 使用结构与类的要求有多严格。但这似乎是使用类以获得您正在寻找的传递引用语义的更自然的地方。

One thought I had is that you could make use of boxing. By creating an interface, say, 'IMap' in your case, to be implanted by your 'Map' struct, and then using a List<IMap>, the list will be holding System.Object objects, which are passed by reference. For example:

我的一个想法是你可以使用拳击。通过创建一个接口,例如,在您的情况下为“IMap”,由您的“Map”结构植入,然后使用 a List<IMap>,该列表将保存 System.Object 对象,这些对象通过引用传递。例如:

interface IMap
{
    int Size { get; set; }
}

struct Map: IMap
{
    public Map(int size)
    {
        _size = size;
    }

    private int _size;

    public int Size
    {
        get { return _size; }
        set { _size = value; }
    }

    public override string ToString()
    {
        return String.Format("Size: {0}", this.Size);
    }

}

Which could then be called by the following:

然后可以通过以下方式调用:

List<IMap> list = new List<IMap>() { 
    new Map(10), 
    new Map(20), 
    new Map(30), 
    new Map(40)};

    list[2].Size = 4;
    Console.WriteLine("list[2].Size = " + list[2].Size.ToString());

Note that these structs would only be boxed once, when passed into the List in the first place, and NOT when called using code such as 'list[2].Size = 4', so it should be fairly efficient, unless you are taking these IMap objects and casting back to Map (copying it out of the List<IMap>) in other parts of your code.

请注意,这些结构只会被装箱一次,当首先传入 List 时,而不是在使用诸如“list[2].Size = 4”之类的代码调用时,因此它应该相当有效,除非您正在使用这些 IMap 对象并List<IMap>在代码的其他部分转换回 Map(将其从 中复制出来)。

Although this would achieve your goal of having direct read-write access to the structs within the List<>, boxing the struct is really stuffing the struct into a class (a System.Object) and I would, therefore, think that it might make more sense to make your 'Map' a class in the first place?

尽管这将实现您对 List<> 中的结构进行直接读写访问的目标,但对结构进行装箱实际上是将结构塞入一个类(System.Object)中,因此我认为它可能会使首先让你的“地图”成为一个班级更有意义?

Mike

麦克风

回答by Joan Venge

I decided to directly replace the result on a copy and reassign the result like:

我决定直接替换副本上的结果并重新分配结果,如:

Map map = arr [ 2 ];
map.Size = 0;
arr [ 2 ] = map;

回答by BinaryConstruct

I keep coming back to this question when trying to calculate normals on a vertex buffer in XNA.

在 XNA 中尝试计算顶点缓冲区上的法线时,我不断回到这个问题。

The best XNA solution I came up with was to copy the data (or store it) in an array.

我想出的最好的 XNA 解决方案是将数据复制(或存储)到一个数组中。

private void SomeFunction()
{
    List<VertexBasicTerrain> vertexList = GenerateVertices();
    short[] indexArray = GenerateIndices();

    CalculateNormals(vertexList, ref indexArray); // Will not work


    var vertexArray = vertexList.ToArray();
    CalculateNormals(ref vertexArray, ref indexArray);
}

// This works (algorithm from Reimers.net)
private void CalculateNormals(ref VertexBasicTerrain[] vertices, ref short[] indices)
{
    for (int i = 0; i < vertices.Length; i++)
        vertices[i].Normal = new Vector3(0, 0, 0);

    for (int i = 0; i < indices.Length / 3; i++)
    {
        Vector3 firstvec = vertices[indices[i * 3 + 1]].Position - vertices[indices[i * 3]].Position;
        Vector3 secondvec = vertices[indices[i * 3]].Position - vertices[indices[i * 3 + 2]].Position;
        Vector3 normal = Vector3.Cross(firstvec, secondvec);
        normal.Normalize();
        vertices[indices[i * 3]].Normal += normal;
        vertices[indices[i * 3 + 1]].Normal += normal;
        vertices[indices[i * 3 + 2]].Normal += normal;
    }
    for (int i = 0; i < vertices.Length; i++)
        vertices[i].Normal.Normalize();
}

// This does NOT work and throws a compiler error because of the List<T>
private void CalculateNormals(List<VertexBasicTerrain> vertices, ref short[] indices)
{
    for (int i = 0; i < vertices.Length; i++)
        vertices[i].Normal = new Vector3(0, 0, 0);

    for (int i = 0; i < indices.Length / 3; i++)
    {
        Vector3 firstvec = vertices[indices[i * 3 + 1]].Position - vertices[indices[i * 3]].Position;
        Vector3 secondvec = vertices[indices[i * 3]].Position - vertices[indices[i * 3 + 2]].Position;
        Vector3 normal = Vector3.Cross(firstvec, secondvec);
        normal.Normalize();
        vertices[indices[i * 3]].Normal += normal;
        vertices[indices[i * 3 + 1]].Normal += normal;
        vertices[indices[i * 3 + 2]].Normal += normal;
    }
    for (int i = 0; i < vertices.Length; i++)
        vertices[i].Normal.Normalize();
}