如何制作一个数组的浅拷贝?

20

我将一个二维数组作为属性传递给了我的用户控件。在那里,我将这些值存储在另一个二维数组中:

int[,] originalValues = this.Metrics;

后来,我更改了this.Metrics中的值。但现在,如果我从originalValues检索值,我会得到来自this.Metrics的更改后的值。如何复制this.Metrics的元素而不仅仅是获取数组的引用?


1
如果有人偶然到这里,现在有一个“Array.Clone()”方法。(.net 4.5) - Aelian
2
@Jeevaka 这个问题特别是在询问如何制作一个数组的 深度 复制。根据 MSDN 文档Array.Clone() "创建一个 浅复制 数组"(强调添加)。 - jmbpiano
1
@jmbpiano:如果我没错的话,'int'是按值存储的,而不是按引用存储的。在这里会发生什么是Array.Clone()将创建一个新的Array对象,并复制由'this.Matrices'引用引用的内存中的任何内容。因为数组的内容不是引用,所以新副本不会与原始副本共享任何内容。如果这些不是'int',而是从'object'派生出来的东西,你的论点就是正确的。我想我们总是可以尝试并查看。 - Aelian
是的,在这里你只需要一个浅拷贝。浅拷贝=1个数据层级深度。由于int是不可变的,因此这就是你所需要的全部。 - Nyerguds
@jmbpiano,我更改了问题的措辞以反映其意图。原帖明显对特定的**浅拷贝(shallow copy)**感兴趣,但使用了错误的术语。 - Ruzihm
9个回答

31

我不知道这是从哪里得来的,但对我很有效。

public static class GenericCopier<T>    //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);
            }
        }
    }

这个比Brian Scott的Array.Copy答案要贵一些。 - Nicholas
8
把这个称为“过度杀伤”就像把超新星称为“砰”的一声:D - Przemysław Wrzesiński
另一种克隆List<T>的方法如下:\n var clonedList = new List(sampleList); - Navap
这对于我来说是最好的解决方案,因为我有嵌套对象的数组。嵌套的对象也可以有数组。还应该提到每个对象必须具有[Serializable]属性才能使其工作。 - rrirower

29

您可以克隆一个数组,这将复制它:

int[,] originalValues = (int[,])this.Metrics.Clone();

31
克隆操作不会进行深度复制(除了对于值类型数组这种简单情况)。 - John Nicholas
15
小秘密:这个问题涉及到一个 int 数组。除此之外的内容都过于复杂了。 - Pieter van Ginkel
3
哈哈,我只是回答了一般性问题。为什么要请求值类型的深拷贝呢? - John Nicholas
我不知道,但在Java中,我总是称之为二维数组,没有人反对;)是的,我只需要它用于整数。 - RoflcoptrException
在.NET中,这是一种数据结构。Clone()就足够了。 - Pieter van Ginkel
显示剩余3条评论

11

你的问题核心在这里:

然后我将这些值存储在另一个二维数组中

事实上这是不准确的。你没有创建一个新的数组;你是将你的originalValues变量设置为相同的数组。有关更详细的解释,请参见下面。


评论区对Pieter's answer的困惑源于"深拷贝"这个术语存在一些不确定性。

在复制对象时,有深拷贝和浅拷贝之分。

深拷贝涉及复制对象所有数据,这意味着如果对象包含自身是复杂类型的成员(例如,用户定义的引用类型实例),那么这些对象也必须进行深度复制(以及它们的所有成员,以此类推)。

浅拷贝仅涉及将一个对象的所有字段复制到另一个对象中,这意味着如果对象包含引用类型,则只需要复制引用(因此,复制后的引用将指向相同的对象)。

就你发布的代码而言:

int[,] originalValues = this.Metrics;

实际上,根本没有复制任何对象。你所做的只是复制一个单一的引用,将this.Metrics(一个引用)的值赋给变量originalValues(也是一个引用,指向相同的数组)。这基本上与简单的值分配相同,例如:

int x = y; // No objects being copied here.

现在,实际上,Array.Clone方法执行的是浅复制。但正如Pieter所指出的那样,对于整数数组的“浅层”或“深层”复制实际上没有区别,因为整数不是复杂对象。
如果你有像这样的东西:
StringBuilder[,] builders = GetStringBuilders();
StringBuilder[,] builderCopies = (StringBuilder[,])builders.Clone();

如果您这样做,最终会得到一个全新的数组(是的,是副本),但其中包含所有相同的StringBuilder对象(因此是浅复制)。这就是深拷贝与浅拷贝的区别所在;如果您想要一个包含builders中所有StringBuilder对象副本的新数组,则需要进行深拷贝。

好的,int x = y; 确实复制了 某些 东西;int 是不可变的,将其分配给一个新变量将始终创建一个副本。但是,是的,在这里基本上发生的就是这样,只是复制的值是数组的 _引用_,而不是数组本身。 - Nyerguds

9
如果您复制的对象是数组,则可以使用以下方法:

如果您复制的对象是数组,则可以使用以下方法:

Array.Copy(sourceArray, destinationArray, sourceArray.Count)

这将为您的目标数组提供原始数组的单独副本。

难道不应该是 Array.Copy(sourceArray, destinationArray, sourceArray.Count()) 或者 Array.Copy(sourceArray, destinationArray, sourceArray.Length) 吗? - FranzHuber23

2

IClonable很好,但除非您在顶级克隆类型中IClonable每个类型,否则您最终会得到引用(据我所知)。

基于此,除非您想遍历对象并克隆其中的每个对象,否则这似乎是最简单的方法。

它很简单,并保证了与原始深层对象的引用之间的干净断裂:

using Newtonsoft.Json;

private T DeepCopy<T>(object input) where T : class
{
    var copy = JsonConvert.SerializeObject((T)input); // serialise to string json object
    var output = JsonConvert.DeserializeObject<T>(copy); // deserialise back to poco
    return output;
}

用法:

var x = DeepCopy<{ComplexType}>(itemToBeCloned);

其中ComplexType是任何想要脱离引用的东西。

它接收任何类型,将其转化为字符串,然后再解析成一个新的副本。

最佳使用示例: 如果您选择了一个复杂类型作为lambda查询的结果,并希望修改结果而不影响原始数据。


2
如果你想要深度复制一个引用类型的数组,可以采用以下方法:
为你的类实现 IClonable 接口,并在内部将所有值类型字段进行深度复制到另一个构造对象中。
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();

1

你可以使用LINQ深度复制一个1维数组。

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

使用二维数组将无法实现此操作,因为二维数组没有实现IEnumerable接口。


0

这里有一个快速解决方案,与某些答案几乎相似,但是提到了 MemberwiseClone

我有仅包含引用值的POCO类。

public class MyPoco {
    public int x { get; set; }
    public int y { get; set; }
    public int z { get; set; }

    // Add a "Clone" method.
    public MyPoco Clone() {
        return (MyPoco)this.MemberwiseClone();
    }
}

然后使用 LINQ 构建一个新的克隆数组:

var myClone = MyPocoArray.Select(x => x.Clone).ToArray();

0

你需要创建一个新数组。然后,你需要手动将每个元素的复制到新数组中。在给定的示例中,你正在创建两个引用同一数组的数组变量。

clone方法的问题在于它是浅拷贝。在这种情况下,因为你正在使用int,所以无所谓。但是,如果你有一个类的数组,ICLonable接口的定义对克隆将会进行多深是不明确的。

想象一下,如果你有一个包含其他类属性的类,而这些类又有其他类属性。clonable接口没有说明它是否也会克隆子成员。而且,许多人对期望行为有不同的看法。

因此,通常建议定义两个接口,IShallowCopyIDeepCopy


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接