复制结构中字段的修改会更改原始结构字段

时间:2017-06-23 10:21:19

标签: c#

在我的研究中,我读到结构是值类型,因此复制结构的更改不会改变原始结构,而不是类。这是我想要和期望的行为。但是,在下面的最小代码示例中,很明显复制结构中的更改反映回原始结构 - 包括列表和数组。这是一个简单的WFA,有一个按钮供您测试。

为什么会这样?我read将结构作为数组成员传递时可能会发生这种情况,我没有这样做,我在类似主题上发现的其他问题也没有显示出这种行为。如果有任何重复或相关问题,我没有找到它们,并会感谢将来如何找到它们的提示。

namespace TestingStructs
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    struct SingleLevel
    {
        public int Level { get; }
        public int Property1 { get; }
        public decimal Property2 { get; }

        public SingleLevel(
            int level,
            int prop1,
            decimal prop2)
        {
            Property1 = prop1;
            Property2 = prop2;
            Level = level;
        }
    }

    struct Levels
    {
        public int NumberOfLevels { get; }
        public readonly List<SingleLevel> RightLevels;
        public readonly SingleLevel[] RightLevelsArray;
        public Levels(int numberofLevels)
        {
            NumberOfLevels = numberofLevels;

            RightLevels = new List<SingleLevel>();
            RightLevelsArray = new SingleLevel[numberofLevels + 1];
            for (int i = 1; i <= 3; i++)
            {
                SingleLevel rightToAdd = new SingleLevel(i, i * 100, 10 + i);
                RightLevels.Add(rightToAdd);
                RightLevelsArray[i - 1] = rightToAdd;
            }
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Levels lastLevels = new Levels(3);
        Levels temporaryLevels = lastLevels;
        MessageBox.Show("temp");
        MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevels.Select(right => right.Property2)));
        MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevelsArray.Select(right => right.Property2)));
        MessageBox.Show("last");
        MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevels.Select(right => right.Property2)));
        MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevelsArray.Select(right => right.Property2)));

        SingleLevel rightLevelToAdd = new SingleLevel(1, 50, 9.5m);
        temporaryLevels.RightLevels.Insert(0, rightLevelToAdd);
        temporaryLevels.RightLevelsArray[0] = rightLevelToAdd;

        MessageBox.Show("temp after insert");
        MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevels.Select(right => right.Property2)));
        MessageBox.Show(string.Join(Environment.NewLine, temporaryLevels.RightLevelsArray.Select(right => right.Property2)));
        MessageBox.Show("last after insert");
        MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevels.Select(right => right.Property2)));
        MessageBox.Show(string.Join(Environment.NewLine, lastLevels.RightLevelsArray.Select(right => right.Property2)));


    }
  }
}

输出:插入后的temp和last包含与Property2完全相同的值。

背景:在我的代码中,我使用的对象实例在运行时可能会也可能不会被修改。因此,我想复制它,修改副本,然后可能将它等回原始对象。如果没有,我不想更改原件,因此不会将任何阶段的修改应用于原件。设计可能存在缺陷,问题在于结构问题。如果您对此有任何提示,我将不胜感激。

3 个答案:

答案 0 :(得分:2)

虽然temporaryLevels实际上是lastLevels的副本,但属性RightLevels和RightLevelsArray仍然引用相同的数据,因为数组和列表是类类型。

在作业中

       Levels temporaryLevels = lastLevels;

您创建了对列表和数组的引用的副本,但没有创建对象本身的引用。

我不知道有任何内置方法可以执行此操作,但您可以在Levels中定义复制构造函数:

public Levels(Levels source)
{
    NumberOfLevels = source.NumberOfLevels;
    RightLevelsArray = new SingleLevel[NumberOfLevels + 1];
    source.RightLevels.CopyTo(RightLevelsArray);
    RightLevels = new List<SingleLevel>();
    RightLevels.AddRange(source.RightLevels);
}

而不是赋值,你可以像这样调用新的构造函数:

        Levels temporaryLevels = new Levels(lastLevels);

现在,temporaryLevels是最后一个级别的深层副本,修改一个结构的列表或数组不会改变另一个结构。

答案 1 :(得分:1)

  

我读到结构是值类型,因此更改了复制   struct不会改变原来的

这不完全正确。 Struct实际上是值类型并存储为一个,如果它包含struct - 它仍然是堆栈中的内联字段,是的。

struct Foo
{
    int A;
    Bar bar;
}

struct Bar
{
    int B;
    int C;
}

sizeof(Foo) == sizeof(int)+sizeof(Bar)

但是,如果您的 struct 包含引用(例如,对于您的类),它将仅内联引用自身(地址值),而不是引用它引用的类。

struct Foo
{
    int A;
    Bar bar;
}

class Bar
{
    int B;
    int C;
}

sizeof(Foo) == sizeof(int)+sizeof(IntPtr)

您的列表是。并且您的struct仅包含对此类型实例的引用。不是实例本身。

答案 2 :(得分:1)

您的问题似乎是在您对代码如何工作的一些假设中。

执行此操作时:

Levels temporaryLevels = lastLevels;

它将创建一个新的Levels结构,其中包含原始值的副本。如果被复制的属性是一个结构,那么它将创建一个新版本,如果它是一个类,那么它将引用同一个对象。这意味着虽然你的两个对象是不同的,但它们中的数组和列表对象是两个都引用相同的类,所以改变一个改变了另一个。

要做你想做的事,你需要创建代码来自己克隆/复制结构。当您进入List和数组时,您需要创建新的数据并使用旧数据填充它们,确保在数据不是结构时创建副本(尽管在这种情况下它们是结构,因此您没有那个问题)。