[英]c# modifying structs in a List<T>
簡短問題:如何修改List
單個項目? (或者更確切地說,存儲在List
中的struct
成員?)
完整說明:
首先,使用下面的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()會做什么? (順便說一句,根據調試器,如果我刪除最后一行並運行它,List會被清除。)
所以我嘗試采用不同的方法,看看它是否使用常規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),我不能使用數組,因為大小需要是動態的。
查看for循環方法, 在編譯錯誤的文檔中給出了原因(和解決方案):
嘗試修改由中間表達式生成但未存儲在變量中的值類型。 當您嘗試直接修改泛型集合中的結構時,可能會發生此錯誤。
要修改結構,首先將其分配給局部變量,修改變量,然后將變量分配回集合中的項目。
因此,在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
方法的調用,因為我認為它沒有添加任何內容。
你在foreach
中遇到的問題是結構是值類型,因此,循環迭代變量實際上不是對列表中結構的引用,而是結構的副本。
我的猜測是編譯器禁止你改變它,因為它很可能不會做你想象的那樣。
subItems.Clear()
不是問題,因為雖然該字段可能是列表中元素的副本,但它也是對列表的引用(淺層副本)。
最簡單的解決方案可能是從struct
變為class
。 或者使用與for (int ix = 0; ix < ...; ix++)
等完全不同的方法。
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;
}
是的,這導致兩個復制操作,但這是您為值語義支付的價格。
您指定的兩個錯誤與您使用結構的事實有關,結構在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;
}
其他答案有更好的信息,為什么結構不同於類,但我認為我可以幫助排序部分。
如果subItems
更改為具體的List而不是IList
接口,那么您將能夠使用Sort
方法。
public List<itemInfo> subItems;
所以你的整個循環變成:
foreach (slotInfo sInf in gAllCats[c].items)
sInf.subItems.Sort();
這根本不需要修改struct
的內容(通常是一件好事)。 struct
的成員仍將指向完全相同的對象。
此外,在C#中使用struct
原因很少。 GC是非常非常好的,除非你在分析器中證明了內存分配瓶頸,否則你最好還是class
。
更簡潔地說,如果items
在gAllCats[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中的類型推斷不是很好嗎?)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.