浅拷贝还是深拷贝?

39

我对深拷贝和浅拷贝这两种方法还不是很熟悉。我感到困惑,无法分辨它们之间的主要区别。虽然我已经阅读了很多相关理论,但我仍需要用适当的示例进行解释。

我有一个程序,在其中将一个对象复制到另一个对象中。

   class A
    {
        public int a = 0;
        public void display()
        {
            Console.WriteLine("The value of a is " + a);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            A ob1 = new A();
            ob1.a = 10;
            ob1.display();
            A ob2 = new A();
            ob2 = ob1;
            ob2.display();
            Console.Read();
        }
    }

这是一个浅拷贝还是深拷贝?请有人提供带有解释的答案。如果是深拷贝,请提供代码来进行同样工作的浅拷贝,反之亦然。

如果上述是浅拷贝,那么即使这个也应该是浅拷贝-->

            A ob1 = new A();
            ob1.a = 10;
            ob1.display();
            A ob2 = ob1;
            ob2.a = 444;
            ob1.display();

维基百科关于对象复制的页面:http://en.wikipedia.org/wiki/Object_copy - Jerome
12
@VaughanHilts - 我不会称其为“浅拷贝”,因为上面的代码根本没有对 obj1 进行任何复制操作... - Alexei Levenkov
1
需要复制的需求是代码异味,也许在这种情况下你应该使用不可变类型。 - Display Name
从标签中可以看出:"浅拷贝包含指向原始变量的链接(内存地址)。对浅拷贝所做的更改会反映在原始对象上。" - Vaughan Hilts
1
通过 A ob2 = new A(); 创建的对象几乎立即被丢弃。下一行将使 ob2 成为与 ob1 相同对象的引用,并且 new A() 直到进行垃圾回收之前才会被清除。 - mpen
显示剩余3条评论
6个回答

60

这里链接:

 

浅拷贝尽可能少地复制。集合的浅拷贝是集合结构的副本,而不是元素。使用浅拷贝,现在有两个集合共享每个元素。

    

深拷贝复制所有内容。集合的深拷贝是包含原始集合中所有元素的两个集合的副本。

你的示例正在创建一个浅拷贝。

A ob1 = new A();
ob1.a = 10;
A ob2 = new A();
ob2 = ob1;

ob1.a = 5; // <-- If you see value of ob2.a after this line, it will be 5.

深拷贝将会 -

 A ob1 = new A();
 ob1.a = 10;
 A ob2 = new A();
 ob2.a = ob1.a;

 ob1.a = 5; // <-- If you see value of ob2.a after this line, it will be 10.

2
我不明白你的意思。因为你没有将ob1分配给ob2,而是将其分配给了变量a。 - Srinivas Cheruku
2
通过将ob2分配给ob1,您正在引用同一实例,即在ob1字段的任何更新将反映在ob2字段中。 - Rohit Vats
3
然而,ob1和ob2拥有它们自己的内存分配,这是不同的,因为我已经具体实例化了它们。那么,ob2如何依赖于指向ob1的内存分配,相反,ob2应该复制来自ob1内存地址的数据,而不是指向它。 - Srinivas Cheruku
2
你误解了内存分配的概念。当你创建两个实例时,会进行单独的内存分配,指向它们自己的引用。但是一旦你将它们等同起来,它们就开始指向相同的引用。请参考此链接以获取详细说明 - https://dev59.com/0lvUa4cB1Zd3GeqPtW2Y - Rohit Vats
2
@RohitVats 第一段代码片段甚至在我看来都不是浅拷贝。ob2 = ob1;只是将引用赋值(正如您在注释中所说),根本没有进行任何复制。 - danio
显示剩余2条评论

18

在我看来,它不是一个严格的浅拷贝或深拷贝。如果必须定义,我会说是浅拷贝。

ob2 = ob1; 这个代码创建了两个对象引用,它们都指向同一个对象。因此,通过 ob1 对对象所做的任何更改都将反映在后续使用 ob2 的地方。

MSDN 上的示例更好地解释了浅拷贝、深拷贝和简单类拷贝之间的区别。

 using System;

    public class IdInfo
    {
        public int IdNumber;

        public IdInfo(int IdNumber)
        {
            this.IdNumber = IdNumber;
        }
    }

    public class Person
    {
        public int Age;
        public string Name;
        public IdInfo IdInfo;

        public Person ShallowCopy()
        {
            return (Person)this.MemberwiseClone();
        }

        public Person DeepCopy()
        {
            Person other = (Person)this.MemberwiseClone();
            other.IdInfo = new IdInfo(this.IdInfo.IdNumber);
            other.Name = String.Copy(this.Name);
            return other;
        }
    }

    public class Example
    {
        public static void Main()
        {
            // Create an instance of Person and assign values to its fields.
            Person p1 = new Person();
            p1.Age = 42;
            p1.Name = "Sam";
            p1.IdInfo = new IdInfo(6565);

            // Perform a shallow copy of p1 and assign it to p2.
            Person p2 = (Person)p1.ShallowCopy();

            // Display values of p1, p2
            Console.WriteLine("Original values of p1 and p2:");
            Console.WriteLine("   p1 instance values: ");
            DisplayValues(p1);
            Console.WriteLine("   p2 instance values:");
            DisplayValues(p2);

            // Change the value of p1 properties and display the values of p1 and p2.
            p1.Age = 32;
            p1.Name = "Frank";
            p1.IdInfo.IdNumber = 7878;
            Console.WriteLine("\nValues of p1 and p2 after changes to p1:");
            Console.WriteLine("   p1 instance values: ");
            DisplayValues(p1);
            Console.WriteLine("   p2 instance values:");
            DisplayValues(p2);

            // Make a deep copy of p1 and assign it to p3.
            Person p3 = p1.DeepCopy();
            // Change the members of the p1 class to new values to show the deep copy.
            p1.Name = "George";
            p1.Age = 39;
            p1.IdInfo.IdNumber = 8641;
            Console.WriteLine("\nValues of p1 and p3 after changes to p1:");
            Console.WriteLine("   p1 instance values: ");
            DisplayValues(p1);
            Console.WriteLine("   p3 instance values:");
            DisplayValues(p3);

            // Make an equal of p1 and assign it to p4.
            Person p4 = new Person();
            p4 = p1;
            // Change the members of the p1 class to new values to show the equal copy.
            p1.Name = "Will";
            p1.Age = 30;
            p1.IdInfo.IdNumber = 8484;
            Console.WriteLine("\nValues of p1 and p4 after changes to p1:");
            Console.WriteLine("   p1 instance values: ");
            DisplayValues(p1);
            Console.WriteLine("   p4 instance values:");
            DisplayValues(p4);
        }

        public static void DisplayValues(Person p)
        {
            Console.WriteLine("      Name: {0:s}, Age: {1:d}", p.Name, p.Age);
            Console.WriteLine("      Value: {0:d}", p.IdInfo.IdNumber);
        }
    }

以下是结果:

Original values of p1 and p2:    p1 instance values:
      Name: Sam, Age: 42
      Value: 6565    p2 instance values:
      Name: Sam, Age: 42
      Value: 6565

Values of p1 and p2 after changes to p1:    p1 instance values:
      Name: Frank, Age: 32
      Value: 7878    p2 instance values:
      Name: Sam, Age: 42
      Value: 7878

Values of p1 and p3 after changes to p1:    p1 instance values:
      Name: George, Age: 39
      Value: 8641    p3 instance values:
      Name: Frank, Age: 32
      Value: 7878

Values of p1 and p4 after changes to p1:    p1 instance values:
      Name: Will, Age: 30
      Value: 8484    p4 instance values:
      Name: Will, Age: 30
      Value: 8484

1
完全不需要使用String.Copy(this.Name)。在C#(以及Java)中,字符串是不可变的,因此原始和深度复制可以共享字符串。 - Djeefther Souza

5

这不是浅拷贝也不是深拷贝,而是引用拷贝。让我来解释一下:变量有两种类型:值类型和引用类型。

值类型是计算机内存中保存变量实际值的(命名)位置。例如:int 是一个值类型,所以当你写下这行代码:

int MyInt = 5;

当这行代码被执行时,运行时将在RAM中找到一个位置并将值5写入其中。因此,如果您搜索该位置,则会发现实际值为5。

相比之下,引用类型是内存中的(命名)位置,它实际上不持有变量的值,而是持有该值存在的内存位置。例如,假设您编写了以下代码:

MyClass myObject = new MyClass();

发生的情况是虚拟机(运行时): 1- 查找可用的内存位置,创建 MyClass 类的实例。假设该对象的位置发生在 RAM 中的字节 #AA3D2 处。
2- 在内存中找到一个位置并创建类型为 MyClass 的引用(引用是指向内存位置的“箭头”),将其命名为“myObject”并将值 AA3D2 存储其中。
现在,如果您查看“myObject”变量,您将不会找到类实例,而是会找到表示保存该类实例的内存位置的 AA3D2。
现在让我们检查 OP 给出的代码:
A ob1 = new A();

这将创建一个名为ob1的变量,创建A类的实例并将该类的位置存储在ob1中。
ob1.a = 10;
ob1.display();

这将改变A类内部的变量a。然后它调用display()方法。

A ob2 = new A();

这里创建了一个名为ob2的变量,创建了A类的实例,并将其位置分配给ob2。

现在,在内存中有两个A类实例和两个变量,每个变量都指向其中一个实例。现在是有趣的部分:     ob2 = ob1;

变量ob2被赋予变量ob1的值。因为ob1包含A类第一个实例的内存位置,现在ob1和ob2都指向内存中的同一位置。使用其中一个变量进行任何操作就等同于使用另一个变量进行相同的操作。

ob2 = ob1意味着复制引用。


2

这是一份浅拷贝,因为如果你修改ob2的变量,然后尝试打印ob1,它们将是一样的。这是因为C#中的类会在它们之间创建链接。如果你想进行深度拷贝,你应该实现一个复制方法并手动复制字段。就像这样:

  class A
    {
        public int a = 0;
        public void display()
        {
            Console.WriteLine("The value of a is " + a);
        }

       public A Copy()
    {
        A a = new A();
        a.a = = this.a;
        return a;
    }



    }

2
我支持@docesam的答案和@Will Yu的部分答案。
这既不是浅拷贝也不是深拷贝,而是引用拷贝。-- docesam
ob2 = ob1; 这段代码创建了两个对象引用,它们都指向同一个对象。因此,通过 ob1 对对象所做的任何更改都将反映在后续使用 ob2 的操作中。--Will Yu
根据MSDN(请参阅备注)的说法:
浅复制只复制数组的元素,无论它们是引用类型还是值类型,但不复制引用所指向的对象。新数组中的引用指向原始数组中引用指向的相同对象。
在这里我们有两点需要注意:
1.浅复制只复制元素。 2.浅复制保留元素的原始引用。
接下来,我将分别解释这两个问题。
首先,我们创建一个具有 Name 属性的 Person 类:
class Person
{
    public string Name {get; set;}
}

然后在 Main() 方法中,我们创建一个 Person 数组。
// Create 2 Persons.
var person1 = new Person(){ Name = "Jack" };
var person2 = new Person(){ Name = "Amy" };

// Create a Person array.
var arrPerson = new Person[] { person1, person2 };

1. 浅拷贝会复制元素。
如果我们在浅拷贝中替换第一个元素,原始数组不应受影响。
// Create a shallow copy.
var arrPersonClone = (Person[]) arrPerson.Clone();

// Replace an element in the shallow copy.
arrPersonClone[0] = new Person(){Name = "Peter"};

// Display the contents of all arrays.
Console.WriteLine( "After replacing the first element in the Shallow Copy" );
Console.WriteLine( $"The Original Array: {arrPerson[0].Name}, {arrPerson[1].Name}" );
Console.WriteLine( $"The Shallow Copy: {arrPersonClone[0].Name}, {arrPersonClone[1].Name}" );

结果:

The Original Array: Jack, Amy
The Shallow Copy: Peter, Amy

2. 浅复制保留元素的原始引用。
如果我们在浅复制中更改元素的属性,则原始数组将受到影响,因为该元素所引用的对象未被复制。
// Create a new shallow copy.
arrPersonClone = (Person[]) arrPerson.Clone();

// Change the name of the first person in the shallow copy.
arrPersonClone[0].Name = "Peter";

// Display the contents of all arrays.
Console.WriteLine( "After changing the Name property of the first element in the Shallow Copy" );
Console.WriteLine( $"The Original Array: {arrPerson[0].Name}, {arrPerson[1].Name}" );
Console.WriteLine( $"The Shallow Copy: {arrPersonClone[0].Name}, {arrPersonClone[1].Name}" );

结果:

The Original Array: Peter, Amy
The Shallow Copy: Peter, Amy

那么一个简单的等号=是如何运作的呢?它会创建一个引用副本。对元素或被引用的对象进行的任何更改都将反映在原始数组和“复制”的数组中。
// Create a reference copy.
var arrPersonR = arrPerson;

// Change the name of the first person.
arrPersonR[0].Name = "NameChanged";
// Replace the second person.
arrPersonR[1] = new Person(){ Name = "PersonChanged" };

// Display the contents of all arrays.
Console.WriteLine( "After changing the reference copy:" );
Console.WriteLine( $"The Original Array: {arrPerson[0].Name}, {arrPerson[1].Name}" );
Console.WriteLine( $"The Reference Copy: {arrPersonR[0].Name}, {arrPersonR[1].Name}" );

结果:

The Original Array: NameChanged, PersonChanged
The Reference Copy: NameChanged, PersonChanged

总之,ob2 = ob1不是浅拷贝,而是引用拷贝。
完整代码如下:
void Main()
{
    // Create 2 Persons.
    var person1 = new Person(){ Name = "Jack" };
    var person2 = new Person(){ Name = "Amy" };

    // Create a Person array.
    var arrPerson = new Person[] { person1, person2 };

    // ----------- 1. A shallow copy copies elements. -----------

    // Create a shallow copy.
    var arrPersonClone = (Person[]) arrPerson.Clone();

    // Replace an element in the shallow copy.
    arrPersonClone[0] = new Person(){Name = "Peter"};

    // Display the contents of all arrays.
    Console.WriteLine( "After replacing the first element in the Shallow Copy:" );
    Console.WriteLine( $"The Original Array: {arrPerson[0].Name}, {arrPerson[1].Name}" );
    Console.WriteLine( $"The Shallow Copy: {arrPersonClone[0].Name}, {arrPersonClone[1].Name}" );

    Console.WriteLine( "\n" );

    // ----------- 2. A shallow copy retains the original references of the elements. -----------

    // Create a new shallow copy.
    arrPersonClone = (Person[]) arrPerson.Clone();

    // Change the name of the first person in the shallow copy.
    arrPersonClone[0].Name = "Peter";

    // Display the contents of all arrays.
    Console.WriteLine( "After changing the Name property of the first element in the Shallow Copy:" );
    Console.WriteLine( $"The Original Array: {arrPerson[0].Name}, {arrPerson[1].Name}" );
    Console.WriteLine( $"The Shallow Copy: {arrPersonClone[0].Name}, {arrPersonClone[1].Name}" );

    Console.WriteLine( "\n" );  

    // ----------- 2. The equal sign. -----------

    // Create a reference copy.
    var arrPersonR = arrPerson;

    // Change the name of the first person.
    arrPersonR[0].Name = "NameChanged";
    // Replace the second person.
    arrPersonR[1] = new Person(){ Name = "PersonChanged" };

    // Display the contents of all arrays.
    Console.WriteLine( "After changing the reference copy:" );
    Console.WriteLine( $"The Original Array: {arrPerson[0].Name}, {arrPerson[1].Name}" );
    Console.WriteLine( $"The Reference Copy: {arrPersonR[0].Name}, {arrPersonR[1].Name}" );
}

class Person
{
    public string Name {get; set;}
}

0
写几行代码来改变第一个对象的属性,然后将其分配给第二个对象。然后调用两个对象的display方法,看看结果如何。这将揭示它实际上是一个浅拷贝。

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