c#修改List 中的结构

简短问题:如何修改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 subItems; } public struct catInfo { public Char catID; public String catDesc; public IList 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 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 sortedSubTemp = new List(); 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已经过时了,支持generics类型(我使用的是3.0),我不能使用数组,因为大小需要是动态的。

查看for循环方法, 在编译错误的文档中给出了原因(和解决方案):

尝试修改由中间表达式生成但未存储在变量中的值类型。 当您尝试直接修改generics集合中的结构时,可能会发生此错误。

要修改结构,首先将其分配给局部变量,修改变量,然后将变量分配回集合中的项目。

因此,在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将始终是引用类型。

IList上使用索引运算符时会发生同样的事情 – 它返回一个副本而不是实际的结构。 如果编译器确实允许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 sortedSubTemp = new List(); 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 subItems; 

所以你的整个循环变成:

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

这根本不需要修改struct的内容(通常是一件好事)。 struct的成员仍将指向完全相同的对象。

此外,在C#中使用struct原因很少。 GC是非常非常好的,除非你在分析器中certificate了内存分配瓶颈,否则你最好还是class

更简洁的是,如果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中的类型推断不是很好吗?)