[英]c# modifying structs in a List<T>
Short question: How can I modify individual items in a List
? 简短问题:如何修改
List
单个项目? (or more precisely, members of a struct
stored in a List
?) (或者更确切地说,存储在
List
中的struct
成员?)
Full explanation: 完整说明:
First, the struct
definitions used below: 首先,使用下面的
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
is populated on load, and so on down the line as the program runs. gAllCats
在加载时填充,在程序运行时依此类推。
The issue arises when I want to sort the itemInfo
objects in the subItems
array. 当我想在
subItems
数组中对itemInfo
对象进行排序时,会出现问题。 I'm using LINQ to do this (because there doesn't seem to be any other reasonable way to sort lists of a non-builtin type). 我正在使用LINQ来执行此操作(因为似乎没有任何其他合理的方法来排序非内置类型的列表)。 So here's what I have:
所以这就是我所拥有的:
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
}
The error is, "Cannot modify members of 'sInf' because it is a 'foreach iteration variable'". 错误是“无法修改'sInf'的成员,因为它是'foreach迭代变量'”。
a, this restriction makes no sense; a,这种限制毫无意义; isn't that a primary use of the foreach construct?
是不是foreach构造的主要用途?
b, (also out of spite) what does Clear() do if not modify the list? b,(也是出于恶意)如果不修改列表,Clear()会做什么? (BTW, the List does get cleared, according to the debugger, if I remove the last line and run it.)
(顺便说一句,根据调试器,如果我删除最后一行并运行它,List会被清除。)
So I tried to take a different approach, and see if it worked using a regular for loop. 所以我尝试采用不同的方法,看看它是否使用常规for循环。 (Apparently, this is only allowable because
gAllCats[c].items
is actually an IList
; I don't think it will allow you to index a regular List
this way.) (显然,这只是允许的,因为
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
}
This time, the error is, "Cannot modify the return value of 'System.Collections.Generic.IList.this[int]' because it is not a variable." 这一次,错误是“无法修改'System.Collections.Generic.IList.this [int]'的返回值,因为它不是变量。” Ugh!
啊! What is it, if not a variable?
它是什么,如果不是变量? and when did it become a 'return value'?
什么时候它成为'回归价值'?
I know there has to be a 'correct' way to do this; 我知道必须有一个'正确'的方法来做到这一点; I'm coming to this from a C background and I know I could do it in C (albeit with a good bit of manual memory management.)
我是从C背景来看这个,我知道我可以在C中做到这一点(尽管有一些手动内存管理。)
I searched around, and it seems that ArrayList
has gone out of fashion in favor of generic types (I'm using 3.0) and I can't use an array since the size needs to be dynamic. 我四处搜索,似乎
ArrayList
已经过时了,支持泛型类型(我使用的是3.0),我不能使用数组,因为大小需要是动态的。
Looking at the for-loop approach, the reason (and solution) for this is given in the documentation for the compilation error : 查看for循环方法, 在编译错误的文档中给出了原因(和解决方案):
An attempt was made to modify a value type that is produced as the result of an intermediate expression but is not stored in a variable.
尝试修改由中间表达式生成但未存储在变量中的值类型。 This error can occur when you attempt to directly modify a struct in a generic collection.
当您尝试直接修改泛型集合中的结构时,可能会发生此错误。
To modify the struct, first assign it to a local variable, modify the variable, then assign the variable back to the item in the collection.
要修改结构,首先将其分配给局部变量,修改变量,然后将变量分配回集合中的项目。
So, in your for-loop, change the following lines: 因此,在for循环中,更改以下行:
catSlots[s].subItems.Clear();
catSlots[s].subItems = sortedSubTemp; // ERROR: see below
...into: ...到:
slotInfo tempSlot = gAllCats[0].items[s];
tempSlot.subItems = sortedSubTemp;
gAllCats[0].items[s] = tempSlot;
I removed the call to the Clear
method, since I don't think it adds anything. 我删除了对
Clear
方法的调用,因为我认为它没有添加任何内容。
The problem you are having in your foreach
is that structs are value types, and as a result, the loop iteration variable isn't actually a reference to the struct in the list, but rather a copy of the struct. 你在
foreach
中遇到的问题是结构是值类型,因此,循环迭代变量实际上不是对列表中结构的引用,而是结构的副本。
My guess would be the compiler is forbidding you change it because it most likely would not do what you expect it to anyway. 我的猜测是编译器禁止你改变它,因为它很可能不会做你想象的那样。
subItems.Clear()
is less of a problem, because altho the field may be a copy of the element in the list, it is also a reference to the list (shallow copy). subItems.Clear()
不是问题,因为虽然该字段可能是列表中元素的副本,但它也是对列表的引用(浅层副本)。
The simplest solution would probably be to change from a struct
to a class
for this. 最简单的解决方案可能是从
struct
变为class
。 Or use a completely different approach with a for (int ix = 0; ix < ...; ix++)
, etc. 或者使用与
for (int ix = 0; ix < ...; ix++)
等完全不同的方法。
The foreach loop doesn't work because sInf
is a copy of the struct inside items. foreach循环不起作用,因为
sInf
是项内部结构的副本。 Changing sInf
will not change the "actual" struct in the list. 更改
sInf
不会更改列表中的“实际”结构。
Clear works because you aren't changing sInf, you are changing the list inside sInf, and Ilist<T>
will always be a reference type. 清除有效,因为您没有更改sInf,您正在更改sInf中的列表,而
Ilist<T>
将始终是引用类型。
The same thing happens when you use the indexing operator on IList<T>
- it returns a copy instead of the actual struct. 当您在
IList<T>
上使用索引运算符时会发生同样的事情 - 它返回一个副本而不是实际的结构。 If the compiler did allow catSlots[s].subItems = sortedSubTemp;
如果编译器确实允许
catSlots[s].subItems = sortedSubTemp;
, you'll be modifying the subItems of the copy, not the actual struct. ,您将修改副本的子项,而不是实际的结构。 Now you see why the compiler says the return value is not a variable - the copy cannot be referenced again.
现在你明白为什么编译器说返回值不是变量 - 副本不能再被引用。
There is a rather simple fix - operate on the copy, and then overwrite the original struct with your copy. 有一个相当简单的修复 - 对副本进行操作,然后用您的副本覆盖原始结构。
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;
}
Yes, this results in two copy operations, but that's the price you pay for value semantics. 是的,这导致两个复制操作,但这是您为值语义支付的价格。
The two errors you specified have to do with the fact that you are using structs, which in C# are value types, not reference types. 您指定的两个错误与您使用结构的事实有关,结构在C#中是值类型,而不是引用类型。
You absolutely can use reference types in foreach loops. 你绝对可以在foreach循环中使用引用类型。 If you change your structs to classes, you can simply do this:
如果将结构更改为类,则可以执行以下操作:
foreach(var item in gAllCats[c].items)
{
item.subItems = item.subItems.OrderBy(x => x.subNum).ToList();
}
With structs this would need to change to: 对于结构,这需要更改为:
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;
}
The other answers have better information on why structs work different than classes, but I thought I could help with the sorting part. 其他答案有更好的信息,为什么结构不同于类,但我认为我可以帮助排序部分。
If subItems
was changed to a concrete List instead of the interface IList
, then you'd be able to use the Sort
method. 如果
subItems
更改为具体的List而不是IList
接口,那么您将能够使用Sort
方法。
public List<itemInfo> subItems;
So your whole loop becomes: 所以你的整个循环变成:
foreach (slotInfo sInf in gAllCats[c].items)
sInf.subItems.Sort();
This won't require the contents of the struct
to be modified at all (generally a good thing). 这根本不需要修改
struct
的内容(通常是一件好事)。 The struct
's members will still point to exactly the same objects. struct
的成员仍将指向完全相同的对象。
Also, there are very few good reasons to use struct
in C#. 此外,在C#中使用
struct
原因很少。 The GC is very, very good, and you'd be better off with class
until you've demonstrated a memory allocation bottleneck in a profiler. GC是非常非常好的,除非你在分析器中证明了内存分配瓶颈,否则你最好还是
class
。
Even more succinctly, if items
in gAllCats[c].items
is also a List
, you can write: 更简洁地说,如果
items
在gAllCats[c].items
也是一个List
,你可以这样写:
gAllCats[c].items.ForEach(i => i.subItems.Sort());
Edit: you give up too easily! 编辑:你太容易放弃了! :)
:)
Sort
is very easy to customise. Sort
很容易定制。 For example: 例如:
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);
That sorts from youngest to oldest. 从最小到最古老的那种。 (Isn't the type inference good in C# 3?)
(C#3中的类型推断不是很好吗?)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.