【问题标题】:Pass struct by value按值传递结构
【发布时间】:2012-05-18 18:08:22
【问题描述】:

鉴于以下代码,我很想知道如何避免以下异常

System.InvalidOperationException was unhandled
Message=Collection was modified; enumeration operation may not execute.
Source=mscorlib
StackTrace:
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at PBV.Program.Main(String[] args) in C:\Documents and Settings\tmohojft\Local Settings\Application Data\Temporary Projects\PBV\Program.cs:line 39
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
InnerException: 

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PBV
{
class Program
{
    struct structItem
    {
        public int y { get; set; }
        public int z { get; set; }
    }

    struct testStruct
    {
        public int x { get; set; }
        public List<structItem> items { get; set; }
    }

    static void Main(string[] args)
    {
        testStruct a = new testStruct();
        structItem b = new structItem();

        for (byte i = 0; i <= 10; i++) {
            b.y = i;
            b.z = i * 2;
            a.items = new List<structItem>();
            a.items.Add(b);
        }

        testStruct c = new testStruct();
        c = a;

        int counter = 0;

        //exception thrown on line below
        foreach (var item in a.items) {
            structItem d = item;
            d.z = 3;

            c.items[counter] = d;
            counter++;
        }

        a = c;
    }
}
}

我最初试图简单地将以下内容放在第二个 foreach 中:

item.z = 3;

但这导致了以下错误:

Cannot modify members of "item" because it is a "foreach iteration" 

我尝试创建一个临时对象以便能够修改 foreach 中的结构数据,但我收到了上述异常。我最好的猜测是因为我的临时结构正在保存对原始结构的引用而不是值本身 - 这导致我的原始结构在我的临时结构被更新时被更新。

所以我的问题是: 如何通过值而不是引用传递这个结构?还是有完全不同的方法来解决这个问题?

提前感谢您的帮助。

编辑:谢谢大家的回答。我知道列表是一个引用类型,但这是否会导致无法通过值而不是引用传递?

【问题讨论】:

  • 朋友不要让朋友使用可变结构:blogs.msdn.com/b/ericlippert/archive/2008/05/14/…
  • 没有真正的“自动”方法来深度复制对象。如果你想做这样的事情,你可以在你的 testStruct 结构上实现 IClonable 并编写代码来在克隆对象中创建一个新列表,然后将每个项目从原始列表复制到克隆对象。

标签: c# .net structure pass-by-reference pass-by-value


【解决方案1】:

我对您的示例代码试图做的事情感到有些困惑,但我认为您的部分困惑可能是当您设置c = a 时,您希望它会复制列表。它不是。虽然结构本身是值类型,但它包含的 items 属性不是。 List 是一个引用类型,因此当您设置c = a 时,它会将items 引用复制到c。因此,当您进入循环时, a 和 c 都包含对同一个列表对象的引用。因此,当您在枚举列表的同时修改列表时,它总是会失败。

避免这种情况的一种简单方法是遍历列表的静态副本:

foreach (var item in a.items.ToArray())

【讨论】:

  • +1,但是 OP 在枚举它时修改可枚举是一个很大的禁忌。考虑为您想做的事情选择不同的策略。
  • @tejs 我承认这不是我真正的代码,只是我正在尝试做的其他事情的简化版本。我知道这并不理想,但我只是将值分配给以前为空的变量,我实际上并没有修改以前设置的任何数据(null 除外)。
  • @SteveDog:看起来可行。一旦可以(再过几个小时),我就会投票,但我想看看在将任何内容标记为答案之前,是否有解决我的想法的答案。干杯!
  • 您是否尝试将其设为List&lt;object&gt;?然后它应该将结构装箱,就好像它是一个引用类型一样。然而,这是以没有类型转换的智能感知为代价的。
  • 我确实想到了,但我不想为这个数据结构创建一个类,因为它相当简单(基本上只是一个结果集)而且我觉得没有必要。跨度>
【解决方案2】:

a.Itemsc.Items 仍然是同一个 List 实例。您可以使用老式的 for 循环对其进行迭代。这样您就不会使用任何枚举器,因此可以根据需要修改列表。

for (int i = 0; i < a.Items.Count; i++)
{
    c.Items[i] = whatever;
}

无论如何,您都在设置柜台,所以这似乎是很自然的做法。出于性能原因,如果您不打算从中添加或删除任何项目,您可能希望将列表大小存储在局部变量中。

【讨论】:

  • 另外一个有用的要提的是,如果你是这样循环的,你可以从末尾开始,然后倒退到列表的开头。
【解决方案3】:

可变值对象(结构)比不可变对象更难处理。考虑使结构不可变并围绕它重新设计代码。

除非您确切知道好处和痛点在哪里,否则根本不使用 struct 通常会更容易。尝试将您的代码切换到类,并衡量性能是否符合您的目标。

注意:c = a; 是浅拷贝(这根本不是你的 testStruct 的第二个拷贝,而是指 a),看起来你想要深拷贝 - 考虑改为构造函数并复制数组而不是引用它。

【讨论】:

    【解决方案4】:

    在某些应用程序中,可变值类型提供最合适的语义。 List&lt;someValueType&gt; 的内在含义通常比 List&lt;someMutableClassType&gt;() 更清晰,无论值类型是否支持除 all-field-copy 之外的任何变异方法( *)。不幸的是,像List&lt;T&gt; 这样的内置集合确实不能很好地支持可变值类型,因为除了全字段复制之外,它们没有公开任何改变存储项的方法。

    我建议在许多情况下,最好不要使用List&lt;T&gt;,而只需保留T[] 和项目计数。如果要向集合中添加一个项目,请检查计数是否大于或等于长度;如果是这样,则将长度加倍。然后将新项目存储在计数指示的槽中并递增计数。

    如果你真的想使用List&lt;someStructType&gt;,你可以使用其他人建议的方法,通过在索引上使用整数循环迭代要修改的项目,而不是使用foreach。然而,在使用可变结构时,使用 myArray[i].X = i+4; 而不是 var temp=myList[i]; temp.X = i+4; myList[i] = temp; 的能力值得处理数组扩展的小麻烦。

    (*) List&lt;MutableClassType&gt; 的语义可能会有所不同,这取决于每个列表项是否包含对不同对象实例的唯一引用,或者是否可以共享包含相同数据的实例。 List&lt;StructNotContainingMutableClassReferences&gt; 不存在这样的歧义。

    (**) 如果XY 是相同结构类型S 的存储位置,则语句X = Y 将以任意顺序将所有公共和私有字段从Y 复制到X。语句X = new S(params); 创建了一个结构类型S 的新实例,所有字段都为空,调用它的构造函数,然后将所有字段从该新实例复制到X。因此,如果值类型实例的任何字段可以保存非默认值,那么如果实例存储在可变存储位置,这些字段将是可变的。存储在不可变位置的值类型实例的所有字段都是不可变的。使值类型“可变”或“不可变”的唯一作用就是影响更改其内容的难易程度。

    【讨论】:

      猜你喜欢
      • 2012-03-04
      • 2015-01-21
      • 1970-01-01
      • 1970-01-01
      • 2014-09-08
      • 1970-01-01
      • 1970-01-01
      • 2014-04-14
      • 2017-01-19
      相关资源
      最近更新 更多