2009-07-01 10 views
15

Pregunta breve: ¿Cómo puedo modificar elementos individuales en un List? (O más precisamente, los miembros de un struct almacenan en un List?)C# modificando las estructuras en una lista <T>

explicación completa:

primer lugar, los struct definiciones utilizadas a continuación:

public struct itemInfo 
{ 
    ...(Strings, Chars, boring)... 
    public String nameStr; 
    ...(you get the idea, nothing fancy)... 
    public String subNum; //BTW this is the element I'm trying to sort on 
} 

public struct slotInfo 
{ 
    public Char catID; 
    public String sortName; 
    public Bitmap mainIcon; 
    public IList<itemInfo> subItems; 
} 

public struct catInfo 
{ 
    public Char catID; 
    public String catDesc; 
    public IList<slotInfo> items; 
    public int numItems; 
} 

catInfo[] gAllCats = new catInfo[31]; 

gAllCats se rellena en la carga, y así sucesivamente en la línea mientras el programa se ejecuta.

El problema surge cuando quiero ordenar los objetos itemInfo en la matriz subItems. Estoy usando LINQ para hacer esto (porque no parece haber otra forma razonable de ordenar listas de tipo no incorporado). Así que aquí es lo que tengo:

foreach (slotInfo sInf in gAllCats[c].items) 
{ 
    var sortedSubItems = 
     from itemInfo iInf in sInf.subItems 
     orderby iInf.subNum ascending 
     select iInf; 
    IList<itemInfo> sortedSubTemp = new List<itemInfo(); 
    foreach (itemInfo iInf in sortedSubItems) 
    { 
     sortedSubTemp.Add(iInf); 
    } 
    sInf.subItems.Clear(); 
    sInf.subItems = sortedSubTemp; // ERROR: see below 
} 

El error es: "No se puede modificar miembros de 'SINF' porque es una 'variable de iteración foreach'".

a, esta restricción no tiene sentido; ¿no es ese un uso principal de la construcción foreach?

b, (también por despecho) ¿Qué hace Clear() si no modifica la lista? (Por cierto, la lista se borra, de acuerdo con el depurador, si elimino la última línea y la ejecuto)

Así que traté de adoptar un enfoque diferente, y ver si funcionaba con un ciclo for normal. (Al parecer, esto sólo es permisible porque gAllCats[c].items es en realidad un IList; no creo que le permita indexar un habitual List esta manera.)

for (int s = 0; s < gAllCats[c].items.Count; s++) 
{ 
    var sortedSubItems = 
     from itemInfo iInf in gAllCats[c].items[s].subItems 
     orderby iInf.subNum ascending 
     select iInf; 
    IList<itemInfo> sortedSubTemp = new List<itemInfo>(); 
    foreach (itemInfo iInf in sortedSubItems) 
    { 
     sortedSubTemp.Add(iInf); 
    } 
    //NOTE: the following two lines were incorrect in the original post 
    gAllCats[c].items[s].subItems.Clear(); 
    gAllCats[c].items[s].subItems = sortedSubTemp; // ERROR: see below 
} 

Esta vez, el error es: "No se puede modificar el devuelve el valor de 'System.Collections.Generic.IList.this [int]' porque no es una variable. " ¡Uf! ¿Qué es, si no es una variable? y cuando se convirtió en un "valor de retorno"?

Sé que tiene que haber una forma "correcta" de hacerlo; Estoy llegando a esto desde el fondo C y sé que podría hacerlo en C (aunque con un buen manejo de la memoria manual)

He buscado y parece que ArrayList ha pasado de moda en a favor de los tipos genéricos (estoy usando 3.0) y no puedo usar una matriz ya que el tamaño debe ser dinámico.

Respuesta

14

Mirando el enfoque de bucle, la razón (y solución) para esto se da en la documentation for the compilation error: Se ha

Un intento para modificar un valor tipo que se produce como resultado de una expresión intermedia pero no es almacenado en una variable. Este error puede ocurrir al cuando intentas directamente modificar una estructura en una colección genérica.

Para modificar la estructura, primero asignarle a una variable local, modificar la variable , a continuación, asignar la variable de nuevo al elemento de la colección.

Así, en sus fines de bucle, cambiar las siguientes líneas:

catSlots[s].subItems.Clear(); 
catSlots[s].subItems = sortedSubTemp; // ERROR: see below 

... en:

slotInfo tempSlot = gAllCats[0].items[s]; 
tempSlot.subItems = sortedSubTemp; 
gAllCats[0].items[s] = tempSlot; 

Quité la llamada al método Clear, desde que don No creo que agregue nada.

+0

Impresionante, eso funciona. ¡Muchas gracias! – andersop

+0

gracias por señalar esto. Sabía sobre esto en cuanto a la modificación de la propiedad de una estructura, pero no sabía que se aplicaba a las colecciones genéricas. Simplemente asumí que la Lista en sí era una clase, por lo que el indexador devolvería una referencia A la estructura. Imagino que así es como funciona con colecciones no genéricas. gracias de nuevo – LoveMeSomeCode

+0

Acabo de encontrar esta solución. Funciona - gracias! –

4

El problema que tiene en su foreach es que las estructuras son tipos de valores y, como resultado, la variable de iteración de bucle no es realmente una referencia a la estructura en la lista, sino una copia de la estructura.

Supongo que el compilador le prohíbe cambiarlo porque lo más probable es que no haga lo que espera de todos modos.

subItems.Clear() es un problema menor, porque aunque el campo puede ser una copia del elemento en la lista, también es una referencia a la lista (copia superficial).

La solución más simple probablemente sería cambiar de struct a class para esto. O use un enfoque completamente diferente con un for (int ix = 0; ix < ...; ix++), etc.

+0

¿Hay alguna manera de hacer un foreach() con un tipo de referencia? Además, RE: el "ser por", traté de que, como se ha señalado ... error diferente, sin embargo. – andersop

+0

Las clases son tipos de referencia ... clases se pueden utilizar en un foreach ... así que sí ... (no sé lo que llevó a esa pregunta en) ... y lo de suficientes detalles acerca de la 'para' enfoque ... I no debe haber prestado suficiente atención a su pregunta. Aunque veo a Fredrik ya aclarado. – jerryjvl

2

El bucle foreach no funciona porque sInf es una copia de la estructura dentro de elementos. Cambiar sInf no cambiará la estructura "actual" en la lista.

Borrar funciona porque no está cambiando sInf, está cambiando la lista dentro de sInf, y Ilist<T> siempre será un tipo de referencia.

Lo mismo ocurre cuando utiliza el operador de indexación en IList<T> - devuelve una copia en lugar de la estructura real. Si el compilador permitió catSlots[s].subItems = sortedSubTemp;, estará modificando los subítems de la copia, no la estructura real. Ahora puede ver por qué el compilador dice que el valor de retorno no es una variable: no se puede hacer referencia a la copia nuevamente.

Hay una solución bastante simple: opere en la copia y sobrescriba la estructura original con su copia.

for (int s = 0; s < gAllCats[c].items.Count; s++) 
{ 
      var sortedSubItems = 
          from itemInfo iInf in gAllCats[c].items[s].subItems 
          orderby iInf.subNum ascending 
          select iInf; 
      IList<itemInfo> sortedSubTemp = new List<itemInfo>(); 
      foreach (itemInfo iInf in sortedSubItems) 
      { 
          sortedSubTemp.Add(iInf); 
      } 
      var temp = catSlots[s]; 
      temp.subItems = sortedSubTemp; 
      catSlots[s] = temp; 
} 

Sí, esto da como resultado dos operaciones de copia, pero ese es el precio que paga por la semántica de los valores.

+0

Ahh, la indexación [] es en realidad una copia también, pero sólo si es en una lista - para una matriz, que es un tipo de valor. Es bueno saberlo, gracias. – andersop

1

Los dos errores que ha especificado tienen que ver con el hecho de que está utilizando estructuras, que en C# son tipos de valores, no tipos de referencia.

Es absolutamente posible utilizar tipos de referencia en los bucles foreach. Si cambia sus estructuras a las clases, puede simplemente hacer esto:

foreach(var item in gAllCats[c].items) 
    { 
     item.subItems = item.subItems.OrderBy(x => x.subNum).ToList(); 
    } 

Con estructuras esto tendría que cambiar a:

for(int i=0; i< gAllCats[c].items.Count; i++) 
    { 
     var newitem = gAllCats[c].items[i]; 
     newitem.subItems = newitem.subItems.OrderBy(x => x.subNum).ToList(); 
     gAllCats[c].items[i] = newitem; 
    } 

Las otras respuestas tienen mejor información sobre por qué estructuras funcionan diferente de clases , pero pensé que podría ayudar con la parte de clasificación.

1

Si subItems se cambió a una lista concreta en lugar de la interfaz IList, entonces sería capaz de utilizar el método Sort.

public List<itemInfo> subItems; 

De modo que todo el bucle se convierte en:

foreach (slotInfo sInf in gAllCats[c].items) 
    sInf.subItems.Sort(); 

Esto no requerirá el contenido de la struct ser modificados en absoluto (por lo general una buena cosa). Los miembros de struct seguirán señalando exactamente los mismos objetos.

También, hay muy pocas buenas razones para utilizar struct en C#. El GC es muy, muy bueno, y estarás mejor con class hasta que hayas demostrado un cuello de botella de asignación de memoria en un generador de perfiles.

Incluso más sucintamente, si items en gAllCats[c].items es también un List, se puede escribir:

gAllCats[c].items.ForEach(i => i.subItems.Sort()); 

Editar: le dan por vencidos fácilmente! :)

Sort es muy fácil de personalizar. Por ejemplo:

var simpsons = new[] 
       { 
        new {Name = "Homer", Age = 37}, 
        new {Name = "Bart", Age = 10}, 
        new {Name = "Marge", Age = 36}, 
        new {Name = "Grandpa", Age = int.MaxValue}, 
        new {Name = "Lisa", Age = 8} 
       } 
       .ToList(); 

simpsons.Sort((a, b) => a.Age - b.Age); 

Eso significa desde el más joven hasta el más viejo. (¿No es la inferencia de tipos bien en C# 3?)

+0

Luego tendría que definir qué significa ordenar esos objetos; realmente quiero ordenar un elemento específico. Supongo que esto implicaría sobrecargar el método Sort() para itemInfo, pero los documentos son realmente terribles sobre ese tipo de cosas. De hecho, hice gAllCats una matriz porque no pude encontrar la manera de manipularla si usaba una lista. Uf ... De todos modos, gracias. – andersop

Cuestiones relacionadas