简短问题:如何修改List
中的单个项目? (或者更准确地说,struct
的成员存储在List
?)
完整的解释:
首先,使用下面的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
在加载时填充,在程序运行时依此类推。
当我想对itemInfo
数组中的subItems
对象进行排序时,会出现此问题。
我正在使用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),因为大小需要是动态的,所以我不能使用数组。 / p>
答案 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
方法的调用,因为我认为它不会添加任何内容。
答案 1 :(得分:4)
你在foreach
中遇到的问题是结构是值类型,因此,循环迭代变量实际上不是对列表中结构的引用,而是对结构的副本结构
我的猜测是编译器禁止你改变它,因为它很可能不会按照你的预期做到。
subItems.Clear()
不是问题,因为虽然字段可能是列表中元素的副本,但它也是对列表的引用(浅拷贝)。
最简单的解决方案可能是从struct
更改为class
。或者使用与for (int ix = 0; ix < ...; ix++)
等完全不同的方法
答案 2 :(得分: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;
}
是的,这会导致两次复制操作,但这是您为价值语义支付的价格。
答案 3 :(得分: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;
}
其他答案有更好的信息,说明为什么结构与类不同,但我认为我可以帮助排序部分。
答案 4 :(得分:1)
如果subItems
已更改为具体列表而非界面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中的类型推断不是很好吗?)