c#修改List <t> </t>中的结构

时间:2009-07-01 05:02:07

标签: c# arrays foreach

简短问题:如何修改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>

5 个答案:

答案 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中的类型推断不是很好吗?)