如何制作数组的深层副本?
我将二维数组作为属性传递给我的用户控件。 在那里我将这些值存储在另一个二维数组中:
int[,] originalValues = this.Metrics;
后来我改变了this.Metrics
值。 但是现在如果我从originalValues中检索值,我会从this.Metrics
获取更改的值。 如何制作深层副本而不只是复制C#中的引用?
您可以克隆一个数组,该数组可以复制它:
int[,] originalValues = (int[,])this.Metrics.Clone();
我不知道从哪里得到这个,但这对我很有用。
public static class GenericCopier //deep copy a list { public static T DeepCopy(object objectToCopy) { using (MemoryStream memoryStream = new MemoryStream()) { BinaryFormatter binaryFormatter = new BinaryFormatter(); binaryFormatter.Serialize(memoryStream, objectToCopy); memoryStream.Seek(0, SeekOrigin.Begin); return (T)binaryFormatter.Deserialize(memoryStream); } } }
如果要复制的对象是数组,则可以使用:
Array.Copy(sourceArray, destinationArray, sourceArray.Count)
这将为您提供原始数组的单独副本到目标数组中。
问题的关键在于:
在那里我将这些值存储在另一个二维数组中
这实际上是不准确的。 你没有创建一个新的数组; 您正在将originalValues
变量设置为相同的数组 。 有关更详细的说明,请参见下文。
在对Pieter的回答的评论中表达的混淆是由于围绕术语“深层复制”的一些不确定性。
在复制对象时,有深度复制和浅层复制。
深度复制涉及复制属于对象的所有数据,这意味着如果对象包含本身很复杂的成员(例如,用户定义的引用类型的实例),那么这些对象也必须进行深度复制(以及他们所有的成员,等等)。
浅复制涉及简单地将所有字段从一个对象复制到另一个对象,这意味着如果对象包含引用类型,则只需要复制引用(因此复制的引用将指向相同的对象)。
对于您发布的代码:
int[,] originalValues = this.Metrics;
……实际上根本没有复制任何对象 。 您所做的就是复制一个引用,将this.Metrics
(一个引用)的值this.Metrics
变量originalValues
(也是一个引用,指向同一个数组)。 这与简单的值赋值基本相同,如下所示:
int x = y; // No objects being copied here.
现在, Array.Clone
方法实际上是一个浅拷贝。 但正如彼得指出的那样, 整数数组的“浅”或“深”副本之间确实没有区别,因为整数不是复杂的对象。
如果你有这样的事情:
StringBuilder[,] builders = GetStringBuilders(); StringBuilder[,] builderCopies = (StringBuilder[,])builders.Clone();
…,你最终会得到一个全新的数组 (副本,是的),但是包含所有相同的StringBuilder
对象(因此是一个浅的副本)。 这是深度与浅度复制发挥作用的地方; 如果您想要一个包含builders
中所有StringBuilder
对象副本的新数组,则需要进行深层复制。
IClonable非常棒,但除非您在顶级克隆类型中使用IClonable
各种类型,否则您最终会得到参考文献AFAIK。
基于此,除非您想要遍历对象并克隆每个对象,否则这似乎是最简单的方法。
它很简单,并保证与原始深层对象的引用完全不同:
using Newtonsoft.Json; private T DeepCopy(object input) where T : class { var copy = JsonConvert.SerializeObject((T)input); // serialise to string json object var output = JsonConvert.DeserializeObject (copy); // deserialise back to poco return output; }
用法:
var x = DeepCopy<{ComplexType}>(itemToBeCloned);
其中ComplexType
是想要从引用中断的任何东西。
它需要输入任何类型,对其进行字符串化,然后将其解串为新副本。
最佳使用示例:如果您因lambda查询而选择了复杂类型,并希望修改结果而不影响原始结果。
您可以使用LINQ对1d数组进行深度复制。
var array = Enumerable.Range(0, 10).ToArray(); var array2 = array.Select(x => x).ToArray(); array2[0] = 5; Console.WriteLine(array[0]); // 0 Console.WriteLine(array2[0]); // 5
使用2d数组,这将无法工作,因为2d数组不实现IEnumerable。
如果要深度复制引用类型数组,可以使用以下方法:
为您的类实现IClonable
iterface,并将所有值类型字段的深层副本内部复制到另一个构造对象。
class A: ICloneable { int field1; public object Clone() { A a= new A(); //copy your fields here a.field1 = this.field1; ... } }
然后你可以使用实际的副本
A[] array1 = new A[]{....}; A[] array2 = array1.Select(a => a.Clone()).ToList();
您需要创建一个新数组。 然后,您需要手动将每个元素的值复制到新数组中。 您在给出的示例中所做的是创建两个引用相同数组的数组变量。
克隆方法的问题在于它是浅拷贝。 在这种情况下,因为你使用的是int
,所以它并不重要。 但是,如果你有一个类数组,那么ICLonable接口的定义就会使克隆的深度变得模糊不清。
想象一下,如果你有一个具有属性的类,这些属性具有其他类的属性。 clonable接口不会声明它是否也将克隆子成员。 此外,许多人对预期的行为有不同的看法。
因此,这就是为什么经常建议定义两个接口, IShallowCopy
和IDeepCopy
。