2017-06-23 72 views
0

在我的研究中,我读到结构是值类型,因此复制结构的更改不会更改原始类型,而不是类。这是我想要和期望的行为。但是,在下面的最小代码示例中,很明显,复制结构中的更改反映回原始 - 对列表和数组都是如此。这是一个简单的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非常相同的值。

背景:在我的代码中,我使用了一个对象实例,该对象实例在运行时可能会被修改,也可能不会被修改。因此,我想复制它,修改副本,然后可能将其等回到原始对象。如果不是这样,我不想改变原文,因此不会将任何阶段的修改应用到原文。设计可能有缺陷,问题是关于结构问题。不过,如果您对此有任何提示,我将不胜感激。

回答

2

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

在分配

 Levels temporaryLevels = lastLevels; 

创建引用列表和数组的一个副本,而不是对象本身的。

我不知道任何内置的方式做到这一点,但你可以定义水平的复制构造函数:

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是去年水平的深拷贝和修改列表或一个结构的数组不会改变其他。

+0

好的..我该如何改变它?我没有看到自己为了我的目的而使用与列表不同的集合,我需要能够复制它们并单独修改。你有什么建议吗? – pun11

+0

我编辑了我的原始答案并添加了一个可能的解决方案。 –

1

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

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

struct Foo 
{ 
    int A; 
    Bar bar; 
} 

struct Bar 
{ 
    int B; 
    int C; 
} 

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

但是,如果你的结构包含(,例如用于你的类)的引用,它只会内联引用自身(地址值),而不是IT类引用指向。

struct Foo 
{ 
    int A; 
    Bar bar; 
} 

class Bar 
{ 
    int B; 
    int C; 
} 

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

您的名单是类。而你的结构只包含对这种类型实例的引用。不是实例本身。

+0

你的答案似乎很有见地,但我不知道这是在做什么诚实..或者它如何帮助我:)但我很想了解,请你详细说明一下吗?顺便说下代码不会在C#中运行 - _'Program.Foo'没有预定义的大小,因此sizeof只能用在不安全的上下文中(可以考虑使用System.Runtime.InteropServices.Marshal.SizeOf_ – pun11

1

你的问题似乎是在你对代码的工作方式做出的一些假设中。

当你这样做:

Levels temporaryLevels = lastLevels; 

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

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

+0

好了,你的解释有助于理解。至于'List'的问题 - 虽然我看到你的观点,但我没有看到任何其他选择,但我明白了你的观点,在编写我自己的克隆/复制方法时如何解决此问题? – pun11

+0

我不清楚为什么你需要列表以及阵列(它感觉像是一个奇怪的设计选择),但处理它很容易。在您的复制代码中,只需创建一个新的'List '复制/每个SingleLevel项目的克隆版本在那里 – Chris

+0

啊,'Array'只是为了测试行为是否相同,在我的真实代码中,我不使用数组。 感谢您的建议。风趣h eocron的回答:_你的名单是一个班级。而你的结构只包含对这种类型实例的引用。不是实例本身。如果我说下面的陈述,我是否正确地理解了这个问题? “如果我通过将其与现有的'List '等同起来创建一个新的'List ',我通过引用(并且修改将改变原来的),但是如果我逐项创建新的'List ',则问题解决了“? – pun11