按值传递struct

时间:2012-05-18 18:08:22

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

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

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中的struct数据,但我收到了上面的异常。我最好的猜测是因为我的临时结构正在保存对原始结构的引用而不是值本身 - 这导致我的原始结构在我的临时结构时被更新。

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

提前感谢您的帮助。

编辑:感谢所有答案的人。我知道列表是一个引用类型,但这是否使得传递值而不是引用是不可能的呢?

4 个答案:

答案 0 :(得分:5)

我对你的示例代码尝试做的事情感到有点困惑,但我想也许你的混淆可能是当你设置c = a时,你希望它能够复制一份列表。它不是。虽然结构本身是值类型,但它包含的items属性却不是。列表与LT;&GT;是一种引用类型,因此当您设置c = a时,它会将items引用复制到c。因此,当您进入循环时,a和c都包含对同一列表对象的引用。因此,当您通过它枚举时修改列表时,它将始终失败。

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

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

答案 1 :(得分:2)

a.Itemsc.Items仍然是完全相同的List实例。您可以使用旧式for循环迭代它。这样你就不会使用任何枚举器,因此可以根据需要修改列表。

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

无论如何你要保留一个计数器,所以这似乎是自然的方式。出于性能原因,如果您不打算在其中添加或删除任何项目,则可能需要将列表大小存储在本地变量中。

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

在某些应用程序中,可变值类型提供最合适的语义。 List<someValueType>的固有含义通常比List<someMutableClassType>)的含义更清晰,无论值类型是否支持除了全场复制之外的任何突变方法( *)。不幸的是,像List<T>这样的内置集合确实不能很好地支持可变值类型,因为除了全字段副本之外,它们不会暴露任何改变存储项的方法。

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

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

(*)List<MutableClassType>的语义含义可能会有所不同,具体取决于每个列表项是否包含对不同对象实例的唯一引用,或者是否可以共享保存相同数据的实例。 List<StructNotContainingMutableClassReferences>没有这种歧义。

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