【问题标题】:c# modifying structs in a List<T>c# 修改 List<T> 中的结构
【发布时间】:2009-07-01 05:02:07
【问题描述】:

小问题:如何修改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 在加载时填充,并在程序运行时依次类推。

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

【问题讨论】:

    标签: c# arrays foreach


    【解决方案1】:

    查看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 方法的调用,因为我认为它没有添加任何内容。

    【讨论】:

    • 感谢您指出这一点。就修改结构属性的属性而言,我知道这一点,但我不知道它适用于泛型集合。我只是假设 List 本身是一个类,所以它的索引器将返回对结构的引用。我想这就是它与非通用集合的工作方式。再次感谢
    【解决方案2】:

    您在foreach 中遇到的问题是结构是值类型,因此,循环迭代变量实际上不是对列表中结构的引用,而是结构的副本。

    我的猜测是编译器禁止你更改它,因为它很可能不会按照你的预期去做。

    subItems.Clear() 问题不大,因为尽管该字段可能是列表中元素的副本,但它也是对列表的引用(浅副本)。

    为此,最简单的解决方案可能是从struct 更改为class。或者使用与for (int ix = 0; ix &lt; ...; ix++) 等完全不同的方法。

    【讨论】:

    • 有没有办法用引用类型做一个 foreach() ?另外,回复:“for”方法,我试过了,如前所述......虽然有不同的错误。
    • 类是引用类型...类可以在 foreach 中使用...所以是的...(不确定是什么引起了这个问题)...抱歉关于 ' 的详细信息不足对于'方法......我一定没有足够重视你的问题。我看到 Fredrik 已经澄清了。
    【解决方案3】:

    foreach 循环不起作用,因为sInf 是项目内部结构的副本。更改 sInf 不会更改列表中的“实际”结构。

    Clear 有效,因为您没有更改 sInf,而是更改了 sInf 中的列表,Ilist&lt;T&gt; 将始终是引用类型。

    当您在IList&lt;T&gt; 上使用索引运算符时会发生同样的事情 - 它返回一个副本而不是实际的结构。如果编译器确实允许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;
    }
    

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

    【讨论】:

    • 啊, indexing[] 实际上也是一个副本,但前提是它在列表中——对于数组,它是一个值类型。很高兴知道,谢谢。
    【解决方案4】:

    您指定的两个错误与您使用的是结构有关,在 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;
        }
    

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

    【讨论】:

      【解决方案5】:

      如果将subItems 更改为具体的List 而不是接口IList,那么您将能够使用Sort 方法。

      public List<itemInfo> subItems;
      

      所以你的整个循环变成:

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

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

      此外,在 C# 中使用 struct 的充分理由很少。 GC 非常非常好,您最好使用class,直到您在分析器中证明内存分配瓶颈。

      更简洁地说,如果gAllCats[c].items 中的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 中的类型推断不是很好吗?)

      【讨论】:

      • 然后我必须定义对这些对象进行排序意味着什么——我真的想对一个特定的项目进行排序。我想这将涉及重载 itemInfo 的 Sort() 方法,但是文档对这种事情真的很糟糕。我实际上将 gAllCats 设为了一个数组,因为如果我使用 List,我无法弄清楚如何操作它。呃……无论如何,谢谢。
      猜你喜欢
      • 2011-03-10
      • 2015-02-08
      • 1970-01-01
      • 2012-12-12
      • 1970-01-01
      • 1970-01-01
      • 2016-07-31
      • 1970-01-01
      • 2013-08-19
      相关资源
      最近更新 更多