Modification directe des éléments de la liste

J’ai cette structure:

struct Map { public int Size; public Map ( int size ) { this.Size = size; } public override ssortingng ToSsortingng ( ) { return Ssortingng.Format ( "Size: {0}", this.Size ); } } 

Lorsque vous utilisez array, cela fonctionne:

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

Mais lorsque vous utilisez List, il ne comstack pas:

 List list = new List ( ) { new Map(10), new Map(20), new Map(30), new Map(40)}; list [ 2 ].Size = 0; 

Pourquoi?

Le compilateur C # vous donnera l’erreur suivante:

Impossible de modifier la valeur de retour de ‘System.Collections.Generic.List.this [int]’ car ce n’est pas une variable

La raison en est que les structures sont des types valeur. Ainsi, lorsque vous accédez à un élément de la liste, vous accédez en fait à une copie intermédiaire de l’élément renvoyé par l’indexeur de la liste.

De MSDN :

Message d’erreur

Impossible de modifier la valeur de retour de ‘expression’ car ce n’est pas une variable

Une tentative de modification d’un type de valeur résultant d’une expression intermédiaire a été effectuée. Parce que la valeur n’est pas persistée, la valeur sera inchangée.

Pour résoudre cette erreur, stockez le résultat de l’expression dans une valeur intermédiaire ou utilisez un type de référence pour l’expression intermédiaire.

Solutions:

  1. Utilisez un tableau. Cela vous donne un access direct aux éléments (vous n’avez pas access à une copie)
  2. Lorsque vous faites de Map une classe, vous pouvez toujours utiliser une liste pour stocker votre élément. Vous obtiendrez alors une référence à un object Map au lieu d’une copie intermédiaire et pourrez modifier l’object.
  3. Si vous ne pouvez pas modifier Mapper de struct en classe, vous devez enregistrer l’élément de liste dans une variable temporaire:
 List list = new List() { new Map(10), new Map(20), new Map(30), new Map(40) }; Map map = list[2]; map.Size = 42; list[2] = map; 

Comme il s’agit d’une struct , lorsque vous utilisez la liste , vous créez des copies.

Lorsque vous utilisez une structure, il est préférable de la rendre immuable. Cela évitera des effets comme celui-ci.

Lorsque vous utilisez un tableau, vous avez un access direct aux structures de mémoire. En utilisant List .get_Item, vous travaillez sur une valeur de retour, c’est-à-dire une copie de la structure.

S’il s’agissait d’une classe, vous obtiendrez une copie d’un pointeur sur la classe, mais vous ne le remarquerez pas, car les pointeurs sont masqués en C #.

Utiliser également List .ToArray ne le résout pas car il créera une copie du tableau interne et le retournera.

Et ce n’est pas le seul endroit où vous obtiendrez de tels effets lorsque vous utiliserez une structure.

La solution fournie par Divo est une très bonne solution de contournement. Mais vous devez vous rappeler de travailler de cette façon, non seulement lorsque vous utilisez la liste , mais partout où vous souhaitez modifier un champ dans la structure.

Je ne suis pas un développeur XNA, je ne peux donc pas dire à quel point il est nécessaire d’utiliser des classes et des structures pour XNA. Mais cela semble être un lieu beaucoup plus naturel d’utiliser une classe afin d’obtenir la sémantique de passage par référence que vous recherchez.

Une des idées que j’ai eu est que vous pourriez utiliser la boxe. En créant une interface, disons ‘IMap’ dans votre cas, à implanter avec votre structure ‘Map’, puis en utilisant un List , la liste contiendra les objects System.Object, qui sont passés par référence. Par exemple:

 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 ssortingng ToSsortingng() { return Ssortingng.Format("Size: {0}", this.Size); } } 

Ce qui pourrait alors être appelé par:

 List list = new List() { new Map(10), new Map(20), new Map(30), new Map(40)}; list[2].Size = 4; Console.WriteLine("list[2].Size = " + list[2].Size.ToSsortingng()); 

Notez que ces structures ne sont encadrées qu’une seule fois, lorsqu’elles sont passées dans la liste en premier lieu, et NON lorsqu’elles sont appelées à l’aide d’un code tel que ‘list [2] .Size = 4’, donc cela devrait être assez efficace, à moins que vous ne preniez ces objects IMap et reconvertis dans Map (le copier hors de la List ) dans d’autres parties de votre code.

Bien que cela puisse vous permettre d’obtenir un access direct en lecture-écriture aux structures de la liste <>, encapsuler la structure est en train de la fourrer dans une classe (un System.Object) et je pense donc que cela pourrait faire en sorte que plus logique de faire de votre “Carte” une classe en premier lieu?

Mike

Je reviens sans cesse sur cette question lorsque je tente de calculer des normales sur un tampon de vertex dans XNA.

La meilleure solution XNA que j’ai proposée consistait à copier les données (ou à les stocker) dans un tableau.

 private void SomeFunction() { List 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 private void CalculateNormals(List 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(); } 

J’ai décidé de remplacer directement le résultat sur une copie et de réaffecter le résultat comme suit:

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

est en fait:

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

Utilisez class à la place de struct .