2009-07-01 12 views
15

Krótkie pytanie: Jak zmodyfikować poszczególne elementy w List? (Lub bardziej precyzyjnie, członkowie struct przechowywane w List?)C# modyfikowanie struktur na liście <T>

Pełne wyjaśnienie:

pierwsze, definicje struct stosowane poniżej:

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 jest wypełniana na obciążenia, i tak dalej wzdłuż linii, gdy program jest uruchomiony.

Problem pojawia się, gdy chcę posortować obiekty itemInfo w tablicy subItems. Używam LINQ, aby to zrobić (ponieważ nie istnieje żaden inny rozsądny sposób sortowania list typu nie-wbudowanego). Więc oto co mam:

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 
} 

Błąd jest, „Nie można modyfikować członków«sInf», ponieważ jest to«foreach iteracji zmienna»”.

a, to ograniczenie nie ma sensu; czy nie jest to podstawowym zastosowaniem konstruktu foreach?

b, (również na przekór), co robi Clear(), jeśli nie modyfikować listy? (BTW, lista zostanie oczyszczona, zgodnie z debuggerem, jeśli usunę ostatnią linię i uruchomię ją.)

Próbowałem więc zastosować inne podejście i sprawdzić, czy zadziałało, używając zwykłej pętli for. (Wydaje się, że to jest tylko dopuszczalna, ponieważ gAllCats[c].items jest rzeczywiście IList; nie sądzę, pozwoli to na indeksie regularny List ten sposób.)

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 
} 

Tym razem jest błąd: „Nie można modyfikować zwracana wartość "System.Collections.Generic.IList.this [int]", ponieważ nie jest zmienną. " Ugh! Co to jest, jeśli nie zmienna? i kiedy stała się "wartością zwrotną"?

Wiem, że musi istnieć "poprawny" sposób, aby to zrobić; Przychodzę do tego z tła C i wiem, że mogłem to zrobić w C (aczkolwiek z dużym ręcznym zarządzaniem pamięcią).

Przeszukałem i wydaje mi się, że ArrayList wyszedł z mody w faworyzowanie typów ogólnych (używam wersji 3.0) i nie mogę używać tablicy, ponieważ rozmiar musi być dynamiczny.

Odpowiedz

14

Patrząc w pętli for podejściu przyczynę (z roztworem) na to podane w documentation for the compilation error:

Próbowano dokonać zmian w wartości typu, który jest wytwarzany w wyniku wyrażenie pośrednie, ale nie jest przechowywane w zmiennej. Ten błąd może wystąpić podczas próby bezpośredniego zmodyfikowania struktury w ogólnej kolekcji .

Aby zmodyfikować struct, najpierw przypisać go do zmiennej lokalnej, należy zmodyfikować zmienną , a następnie przypisać zmienną z powrotem do elementu w kolekcji.

Tak w pętli for, zmień następujące linie:

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

... na:

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

usunąłem wywołanie metody Clear, bo don myślę, że to coś dodaje.

+0

Niesamowite, to działa. Wielkie dzięki! – andersop

+0

dzięki za wskazanie tego. Wiedziałem o tym, jeśli chodzi o modyfikowanie właściwości właściwości struct, ale nie wiedziałem, że ma ona zastosowanie do kolekcji ogólnych. Po prostu założyłem, że sama lista była klasą, więc indeksator zwróci referencję do struktury. Wyobrażam sobie, że tak to działa z nietypowymi kolekcjami. Jeszcze raz dziękuję – LoveMeSomeCode

+0

Właśnie znalazłem to rozwiązanie. Działa - dzięki! –

4

Problem w twoim foreach polega na tym, że struktury są typami wartości iw rezultacie zmienna pętli iteracji nie jest w rzeczywistości odniesieniem do struktury na liście, ale raczej kopią struktury.

Domyślam się, że kompilator zabrania ci go zmienić, ponieważ najprawdopodobniej nie zrobiłby tego, czego się spodziewasz.

subItems.Clear() jest mniejszy problem, bo chociaż pola może być kopią element listy jest również odniesienie do listy (płytkie kopii).

Najprostszym rozwiązaniem będzie prawdopodobnie zmiana z struct na class. Lub użyj zupełnie innego podejścia z for (int ix = 0; ix < ...; ix++) itp.

+0

Czy istnieje sposób na wykonanie foreach() z typem odniesienia? Również, re: "na" podejście, próbowałem, jak zauważono ... inny błąd, chociaż. – andersop

+0

Klasy są typami referencyjnymi ... Klasy mogą być używane w foreach ... więc tak (nie jestem pewien, na czym postawiono to pytanie) ... i przepraszam za niewystarczające szczegóły dotyczące podejścia "za" ... nie mogłeś poświęcić wystarczającej uwagi Twojemu pytaniu. Widzę jednak, że Fredrik już wyjaśnił. – jerryjvl

2

Pętla foreach nie działa, ponieważ sInf jest kopią elementów wewnątrz struktury. Zmiana sInf nie zmieni "faktycznej" struktury na liście.

Wyczyść działa, ponieważ nie zmieniasz sInf, zmieniasz listę wewnątrz sInf, a Ilist<T> zawsze będzie typem odniesienia.

To samo dzieje się, gdy używasz operatora indeksowania na IList<T> - zwraca kopię zamiast rzeczywistej struktury. Jeśli kompilator zezwolił na catSlots[s].subItems = sortedSubTemp;, będziesz modyfikował podelementy kopii, a nie rzeczywistą strukturę. Teraz widzisz, dlaczego kompilator mówi, że zwracana wartość nie jest zmienną - kopia nie może być ponownie przywołana.

Istnieje dość prosta naprawa - operacja na kopii, a następnie zastąpienie oryginalnej struktury kopią.

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; 
} 

Tak, powoduje to dwie operacje kopiowania, ale jest to cena, jaką płaci się za semantykę wartości.

+0

Ahh, indeksowanie [] jest w rzeczywistości także kopią, ale tylko jeśli znajduje się na liście - dla tablicy jest to typ wartości. Dobrze wiedzieć, dzięki. – andersop

1

Dwa wymienione błędy mają związek z faktem, że używasz struktur, które w języku C# są typami wartości, a nie typami referencyjnymi.

Absolutnie można używać typów odniesienia w pętlach foreach. Jeśli zmienisz swoje struktury do klas, można po prostu to zrobić:

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

Z elemencie byłoby to trzeba zmienić na:

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; 
    } 

innych odpowiedzi mają lepszy dostęp do informacji o tym, dlaczego kodowanym działa inaczej niż klas , ale pomyślałem, że mogę pomóc przy sortowaniu.

1

Jeśli zmieniono subItems na konkretną listę zamiast interfejsu IList, można użyć metody Sort.

public List<itemInfo> subItems; 

Więc cała pętla staje:

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

To nie będzie wymagać zawartości struct zostać zmodyfikowane w ogóle (na ogół dobra rzecz). Członkowie struct nadal będą wskazywać dokładnie te same obiekty.

Istnieje również kilka dobrych powodów, aby używać struct w języku C#. GC jest bardzo, bardzo dobry, i lepiej byłoby, gdybyś miał class, aż zademonstrujesz wąskie gardło alokacji pamięci w profilerze.

Jeszcze bardziej zwięźle, jeśli items w gAllCats[c].items jest również List, można napisać:

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

Edycja: dać się zbyt łatwo! :)

Sort jest bardzo łatwy do dostosowania. Na przykład:

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

To od najmłodszych do najstarszych. (Czy nie jest dobrym wnioskiem typu w C# 3?)

+0

Następnie musiałbym zdefiniować, co to znaczy sortować te obiekty - naprawdę chcę posortować jeden konkretny przedmiot. Przypuszczam, że wiązałoby się to z przeciążeniem metody Sort() dla itemInfo, ale dokumenty są naprawdę okropne. Faktycznie uczyniłem gAllCats tablicą, ponieważ nie mogłem wymyślić, jak ją zmanipulować, gdybym użył Listy. Ugh .... W każdym razie, dzięki. – andersop