C#基础:C#中的深拷贝和浅拷贝

一、什么是深拷贝和浅拷贝

对于所有面向对象的语言,复制永远是一个容易引发讨论的题目,C#中也不例外。此类问题在面试中极其容易被问到,我们应该在了解浅拷贝和深拷贝基本概念的基础上,从设计的角度进一步考虑如何支持对象的拷贝。

在System.Object类中,有一个受保护的方法object.MemberwiseClone(),这个方法实现了对象的复制。事实上,它所实现的就是我们所称的浅拷贝。

所谓的浅拷贝,是指拷贝一个对象的时候,拷贝原始对象中所有的非静态值类型成员和所有的引用类型成员的引用。换言之,新的对象和原始对象将共享所有引用类型成员的实际对象。而相对的,深拷贝是指不仅复制所有的非静态值类型成员,而且也复制所有引用类型成员的实际对象。深拷贝和浅拷贝的概念是递归的,也就是说当引用类型成员中包含另外一个引用类型成员时,拷贝的时候将对其内部成员实行同样的复制策略。

浅拷贝示意图如下所示:

C#基础:C#中的深拷贝和浅拷贝

深拷贝示意图如下图所示:

C#基础:C#中的深拷贝和浅拷贝

类型基类System.Object已经为所有类型都实现了浅拷贝,类型所要做的就是公开一个复制的接口,而通常的,这个接口会借由实现ICloneable接口来实现。ICLoneable只包含一个Clone方法。该方法既可以被实现为浅拷贝也可以被实现为深拷贝,具体如何取舍需要根据具体类型的需求来决定。下面的代码提供了一个深拷贝的简单示例:

using System;

namespace DeepCopy
{
    class Program
    {
        static void Main(string[] args)
        {
            // 定义原始对象
            DpCopy dc = new DpCopy();
            dc._i = 10;
            dc._a = new A();

            // 定义深拷贝对象
            DpCopy deepClone = (DpCopy)dc.Clone();
            // 定义浅拷贝对象
            DpCopy shadowclone = (DpCopy)dc.MemberwiseClone();
            // 深拷贝的复制对象将拥有自己的引用类型成员对象
            // 所以这里的赋值不会影响原始对象
            deepClone._a._s = "我是深拷贝的A";
            Console.WriteLine(dc);
            Console.WriteLine(deepClone);
            Console.WriteLine("\r\n");

            // 浅拷贝的复制对象共享原始对象的引用类型成员对象
            // 所以这里的赋值将影响原始对象
            shadowclone._a._s = "我是浅拷贝的A";
            Console.WriteLine(dc);
            Console.WriteLine(shadowclone);

            Console.ReadKey();
        }
    }

    public class DpCopy : ICloneable
    {
        public int _i = 0;
        public A _a = new A();
        public object Clone()
        {
            // 实现深拷贝
            DpCopy newDc = new DpCopy();
            // 重新实例化一个引用类型变量
            newDc._a = new A();
            // 给新引用类型变量的成员值
            newDc._a._s = _a._s;
            newDc._i = _i;
            return newDc;
        }

        // 实现浅拷贝
        public new object MemberwiseClone()
        {
            return base.MemberwiseClone();
        }

        /// <summary>
        /// 重写类的ToString()方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "I的值为:" + _i.ToString() + ",A为:" + _a._s;
        }
    }

    /// <summary>
    /// 包含一个引用成员的类型
    /// </summary>
    public class A
    {
        public string _s = "我是原始A";
    }
}

在上面的代码中,类型DpCopy通过ICLoneable接口的Clone方法提供了深拷贝,并且通过提供一个MemberwiseClone的公共方法提供了浅拷贝。DpCopy类型具有一个值类型成员和一个引用类型成员,引用类型成员在浅拷贝和深拷贝时将展现不同的特性,浅拷贝的原始对象和目标对象公用了一个引用类型成员对象,这在程序的执行结果中可以清楚地看到:

C#基础:C#中的深拷贝和浅拷贝

有的参考资料上说C#中的深拷贝通过ICloneable接口来实现。这句话并不正确。事实上任何名字的方法都可以用来实现深拷贝,并且没有任何语法来规定深拷贝只能通过Clone方法来实现。Clone这个名字只是一种习惯的称呼,而实现ICloneable只能带来一般接口的通用便利性,而并没有任何关于拷贝的特殊性。

一般可被继承的类型应该避免实现ICloneable接口,因为这样做将强制所有的子类型都需要实现ICloneable接口,否则将使类型的深拷贝不能覆盖子类的新成员。

二、总结

浅拷贝是指复制类型中的所有值类型成员,而只赋值引用类型成员的引用,并且使目标对象共享原对象的引用类型成员对象。深拷贝是指同时复制值类型成员和引用类型成员的对象。浅拷贝和深拷贝的概念都是递归的。System.Object中的MemberwiseClone已经实现了浅拷贝,但它是一个受保护的方法。无论深拷贝还是浅拷贝,都可以通过实现ICloneable接口的Clone方法来实现,可被继承的类型需要谨慎地实现ICloneable接口,因为这将导致所有的子类型都必须实现ICloneable接口。

C#基础:C#中的深拷贝和浅拷贝

上一篇:C# 关键字Abstract


下一篇:Delphi异常处理机制