直接修改List <t>元素</t>

时间:2009-01-06 00:00:53

标签: c# .net

我有这个结构:

struct Map
{
    public int Size;

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

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

使用数组时,它可以工作:

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

arr [ 2 ].Size = 0;

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

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

list [ 2 ].Size = 0;

为什么?

6 个答案:

答案 0 :(得分:45)

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

  

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

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

来自MSDN

  

错误消息

     

无法修改返回值   '表达',因为它不是   变量

     

尝试修改值   类型是一个结果   中间表达。因为   值不会持久化,值会   保持不变。

     

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

解决方案:

  1. 使用数组。这使您可以直接访问元素(您没有访问副本)
  2. 当您创建Map类时,您仍然可以使用List来存储元素。然后,您将获得对Map对象的引用而不是中间副本,您将能够修改该对象。
  3. 如果您无法将Map从struct更改为类,则必须将列表项保存在临时变量中:
  4. 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;
    

答案 1 :(得分:17)

因为它是struct,所以当使用List&lt; T&gt;时,您正在创建副本。

使用结构时,最好使它们不可变。这样可以避免这样的效果。

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

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

同样使用List&lt; T&gt; .ToArray无法解决它,因为它将创建内部数组的副本,并返回此内容。

这不是唯一一个在使用结构时获得这样效果的地方。

Divo提供的解决方案是一个非常好的解决方法。但是你必须记住以这种方式工作,而不仅仅是在使用List&lt; T&gt;时。但到处都想改变结构中的一个字段。

答案 2 :(得分:2)

我不是XNA开发人员,因此我无法评论使用结构与XNA类的要求有多严格。但这似乎是一个更自然的地方,使用一个类来获得你正在寻找的pass-by-ref语义。

我有一个想法是你可以利用拳击。通过在您的情况下创建一个接口,比如“IMap”,由“Map”结构植入,然后使用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);
    }

}

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

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

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

虽然这可以实现你对List&lt;&gt;中的结构进行直接读写访问的目标,但是装结结构实际上是将结构填充到一个类(System.Object)中,因此我会认为首先让你的“地图”成为一个班级更有意义吗?

麦克

答案 3 :(得分:2)

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

我提出的最佳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();
}

答案 4 :(得分:1)

list [ 2 ].Size = 0;

实际上是:

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

使用class代替struct

答案 5 :(得分:1)

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

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