我有这个结构:
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;
为什么?
答案 0 :(得分:45)
C#编译器会给你以下错误:
无法修改'System.Collections.Generic.List.this [int]'的返回值,因为它不是变量
原因是结构是值类型,因此当您访问列表元素时,您实际上将访问由列表的索引器返回的元素的中间副本。
来自MSDN:
错误消息
无法修改返回值 '表达',因为它不是 变量
尝试修改值 类型是一个结果 中间表达。因为 值不会持久化,值会 保持不变。
要解决此错误,请存储 表达式的结果 中间值,或使用参考 中间表达式的类型。
解决方案:
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;