2009-07-01 33 views
15

簡短問題:如何修改List中的單個項目? (或者更準確地說,一個struct成員存儲在List?)c#修改列表中的結構<T>

完整的解釋:

首先,下面所使用的struct定義:

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填充上的負載,等等隨着程序運行而下降。

當我想對subItems陣列中的itemInfo對象進行排序時,會出現此問題。 我使用LINQ來做到這一點(因爲似乎沒有任何其他合理的方式來排序非內置類型的列表)。 因此,這裏是我有:

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 
} 

的錯誤是,「無法修改‘SINF’的成員,因爲它是一個‘的foreach迭代變量’」。

a,這個限制沒有意義;這不是foreach構造的主要用途嗎?

b,(也是出於同情)如果不修改列表,Clear()會做什麼? (順便說一句,根據調試器,列表清除,如果我刪除最後一行並運行它)。

所以我試着採取不同的方法,看看它是否使用正則for循環。 (顯然,這是因爲gAllCats[c].items實際上是一個IList是唯一允許的,我不認爲這將讓你指數定期List這種方式。)

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 
} 

這一次,錯誤的是,「不能修改返回'System.Collections.Generic.IList.this [int]'的值,因爲它不是一個變量。「啊!它是什麼,如果不是一個變量?它何時成爲「回報價值」?

我知道必須有一個「正確」的方法來做到這一點;我來自C背景,我知道我可以在C做(儘管有很好的手動內存管理)。

我搜索了一下,看起來ArrayList已經過時了支持泛型類型(我使用的是3.0),並且我不能使用數組,因爲大小需要是動態的。

回答

14

尋找在for循環方法中,原因(和溶液)此在documentation for the compilation error給出:

試圖修改在製造的 結果的值 類型一箇中間表達式,但不是 存儲在一個變量中。當您嘗試直接 修改通用 集合中的結構時,會發生此錯誤 。

要修改結構,首先將其分配給局部變量 ,修改 變量,然後將變量 分配回集合中的項目。

所以,在你的for循環,更改以下行:

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

...到:

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

我去掉了調用Clear方法,因爲我不不認爲它增加了任何東西。

+0

太棒了,有效。非常感謝! – andersop 2009-07-01 06:01:57

+0

感謝您指出了這一點。就修改struct屬性的屬性而言,我知道這一點,但我不知道它應用於泛型集合。我只是假定List本身就是一個類,所以它的索引器會返回一個對結構體的引用。我想象它是如何與非泛型集合一起工作的。再次感謝 – LoveMeSomeCode 2009-10-26 16:31:21

4

您在foreach中遇到的問題是結構是值類型,因此循環迭代變量實際上不是對列表中的結構的引用,而是結構的副本。

我的猜測是編譯器禁止你改變它,因爲它很可能不會做你期望的。

subItems.Clear()不是一個問題,因爲雖然該字段可能是列表中元素的副本,但它也是對列表(淺表副本)的引用。

最簡單的解決方案可能是將其從struct更改爲class。或者使用與for (int ix = 0; ix < ...; ix++)等完全不同的方法

+0

有什麼辦法做一個引用類型的foreach()? 此外,重新:「爲」的方法,我試過,如指出......不同的錯誤,但。 – andersop 2009-07-01 05:27:41

+0

類是引用類型...類可以在foreach中使用......所以是......(不知道是什麼引發了這個問題)......並且對'for'方法的詳細信息抱歉抱歉... I一定不會對你的問題給予足夠的重視。我看到Fredrik已經澄清。 – jerryjvl 2009-07-01 14:00:56

2

foreach循環不起作用,因爲sInf是項目內部結構的副本。更改sInf不會更改列表中的「實際」結構。

清除工作,因爲你沒有改變sInf,你正在改變sInf內的列表,Ilist<T>將永遠是一個引用類型。

當您在IList<T>上使用索引操作符時,會發生同樣的事情 - 它將返回一個副本,而不是實際的結構體。如果編譯器確實允許catSlots[s].subItems = sortedSubTemp;,那麼您將修改副本的子項,而不是實際的結構。現在你明白爲什麼編譯器說返回值不是變量 - 副本不能再被引用。

有一個相當簡單的修復 - 對副本進行操作,然後用您的副本覆蓋原始結構。

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

是的,這將導致兩個複製操作,但是這是你付出值語義的價格。

1

您指定的兩個錯誤必須與您使用的結構相關,而這些結構在C#中是值類型,而不是引用類型。

你絕對可以在foreach循環中使用引用類型。如果您改變結構的類,你可以簡單地這樣做:

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

對於結構,這將需要更改爲:

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

其他的答案對爲什麼結構的工作比類不同的更好的信息,但我認爲我可以幫助分揀部分。

1

如果subItems被更改爲一個具體的List而不是接口IList,那麼你就可以使用Sort方法。

public List<itemInfo> subItems; 

所以你的整個循環就變成了:

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

這將不需要0​​的內容都被修改(通常是一件好事)。 struct的成員仍將指向完全相同的對象。

另外,在C#中使用struct的原因很少。 GC是非常非常好的,在分析器上演示內存分配瓶頸之前,您最好用class

更簡潔,如果itemsgAllCats[c].items也是List,你可以寫:

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

編輯:你太輕易放棄! :)

Sort非常容易定製。例如:

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

從最年輕到最古老。 (是不是在C#3類型推斷良好?)