c#修改List<T>中的结构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1067340/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 07:25:34  来源:igfitidea点击:

c# modifying structs in a List<T>

c#arraysforeach

提问by andersop

Short question: How can I modify individual items in a List? (or more precisely, members of a structstored in a List?)

简短的问题:如何修改List? 中的单个项目?(或者更准确地说,a 的成员struct存储在 a 中List?)

Full explanation:

完整解释:

First, the structdefinitions 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];

gAllCatsis populated on load, and so on down the line as the program runs.

gAllCats在加载时填充,并在程序运行时沿线向下填充。

The issue arises when I want to sort the itemInfoobjects in the subItemsarray. 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). So here's what I have:

当我想对数组中的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
}

The error is, "Cannot modify members of 'sInf' because it is a 'foreach iteration variable'".

错误是“无法修改 'sInf' 的成员,因为它是一个 'foreach 迭代变量'”。

a, this restriction makes no sense; isn't that a primary use of the foreach construct?

a、这个限制没有意义;这不是 foreach 结构的主要用途吗?

b, (also out of spite) what does Clear() do if not modify the list? (BTW, the List does get cleared, according to the debugger, if I remove the last line and run it.)

b,(也是出于恶意)如果不修改列表,Clear() 会做什么?(顺便说一句,如果我删除最后一行并运行它,根据调试器,列表确实会被清除。)

So I tried to take a different approach, and see if it worked using a regular for loop. (Apparently, this is only allowable because gAllCats[c].itemsis actually an IList; I don't think it will allow you to index a regular Listthis way.)

所以我尝试采用不同的方法,看看它是否可以使用常规的 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
}

This time, the error is, "Cannot modify the return value of 'System.Collections.Generic.IList.this[int]' because it is not a variable." Ugh! What is it, if not a variable? and when did it become a 'return value'?

这一次,错误是“无法修改 'System.Collections.Generic.IList.this[int]' 的返回值,因为它不是变量。” 啊! 如果不是变量,它是什么?什么时候变成了“返回值”?

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 ArrayListhas 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)并且我不能使用数组,因为大小需要是动态的。

采纳答案by Fredrik M?rk

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 Clearmethod, since I don't think it adds anything.

我删除了对该Clear方法的调用,因为我认为它没有添加任何内容。

回答by jerryjvl

The problem you are having in your foreachis 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 structto a classfor this. Or use a completely different approach with a for (int ix = 0; ix < ...; ix++), etc.

最简单的解决方案可能会从改变structclass这一点。或者对 afor (int ix = 0; ix < ...; ix++)等使用完全不同的方法。

回答by Senthil Kumar

The foreach loop doesn't work because sInfis a copy of the struct inside items. Changing sInfwill not change the "actual" struct in the list.

foreach 循环不起作用,因为它sInf是项目内部结构的副本。更改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.

Clear 有效,因为您没有更改 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. If the compiler did allow 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.

当您使用索引运算符时会发生同样的事情IList<T>- 它返回一个副本而不是实际的结构。如果编译器确实允许catSlots[s].subItems = sortedSubTemp;,您将修改副本的子项,而不是实际的结构。现在您明白为什么编译器说返回值不是变量 - 无法再次引用副本。

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.

是的,这会导致两次复制操作,但这就是您为值语义付出的代价。

回答by NerdFury

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. If you change your structs to classes, you can simply do this:

您绝对可以在 foreach 循环中使用引用类型。如果您将结构更改为类,您可以简单地执行以下操作:

    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.

其他答案提供了更好的信息,说明为什么结构与类的工作方式不同,但我认为我可以在排序部分提供帮助。

回答by Daniel Earwicker

If subItemswas changed to a concrete List instead of the interface IList, then you'd be able to use the Sortmethod.

如果subItems更改为具体 List 而不是 interface 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 structto be modified at all (generally a good thing). The struct's members will still point to exactly the same objects.

这根本不需要struct修改的内容(通常是一件好事)。该struct的成员将仍然指向相同的目标。

Also, there are very few good reasons to use structin C#. The GC is very, very good, and you'd be better off with classuntil you've demonstrated a memory allocation bottleneck in a profiler.

此外,struct在 C# 中使用的理由很少。GC 非常非常好,class除非您在分析器中证明内存分配瓶颈,否则您会更好。

Even more succinctly, if itemsin gAllCats[c].itemsis also a List, you can write:

更简洁地说,如果itemsin gAllCats[c].items也是 a List,你可以写:

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

Edit:you give up too easily! :)

编辑:你太容易放弃了!:)

Sortis very easy to customise. For example:

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

That sorts from youngest to oldest. (Isn't the type inference good in C# 3?)

从最年轻到最年长的排序。(C# 3 中的类型推断不是很好吗?)