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

摘要:
事实上,任何名称方法都可以用于实现深度复制,并且没有语法指定深度复制只能通过克隆方法实现。克隆这个名字只是一个习惯的名字,而实现ICloneable只能带来通用界面的一般便利,复制没有特殊性。
一、什么是深拷贝和浅拷贝

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

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

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

浅拷贝示意图如下所示:

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

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

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

类型基类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("
");

            // 浅拷贝的复制对象共享原始对象的引用类型成员对象
            // 所以这里的赋值将影响原始对象
            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#中的深拷贝和浅拷贝第3张

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

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

二、总结

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

免责声明:文章转载自《C#基础:C#中的深拷贝和浅拷贝》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Flask结合ECharts实现在线可视化效果,超级详细!提高程序运行效率的10个简单方法(转)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C# 中 equals( ) 和 == 的区别和用法

Equals: 下面的语句中,x、y 和 z 表示不为 null 的对象引用。* 除涉及浮点型的情况外,x.Equals(x) 都返回 true。 * x.Equals(y) 返回与 y.Equals(x) 相同的值。 * 如果 x 和 y 都为 NaN,则 x.Equals(y) 返回 true。 * 当且仅当 x.Equals(z) 返回 true 时...

【javascript】浅析js中的堆和栈

这里先说两个概念:1、堆(heap)2、栈(stack)堆 是堆内存的简称。栈 是栈内存的简称。说到堆栈,我们讲的就是内存的使用和分配了,没有寄存器的事,也没有硬盘的事。各种语言在处理堆栈的原理上都大同小异。堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放。 javascript的基本类型就5种:Unde...

C#编程(四)

原文地址:http://blog.csdn.net/shanyongxu/article/details/46400067 C#预定义数据类型 C#中的可用类型以及及其定义非常严格,C#中获得数据类型分为两种,第一种是值类型,第二种是引用类型.区别是值类型直接存储值,引用类型存储值的引用.值类型存储在堆栈中,引用类型存储在托管堆中.要能区分值类型还是引用类...

java int与integer的区别

java int与integer的区别 int与integer的区别从大的方面来说就是基本数据类型与其包装类的区别: int 是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象 1.Java 中的数据类型分为基本数据类型和复杂数据类型 int 是前者而integer 是后者(也就是一个类);因此在类进行初始化时int类的变量初始为0...

C# 基本数据类型

Ø  前言 每个编程语言都有基本的数据类型,例如 C、C++、Java、Python、PHP、JavaScript、以及各种数据库等,而 C# 也不例外。本篇主要讨论 C# 中的一些常用的基础数据类型。   1.   值类型 Ø  值类型隐式继承于 System.ValueType,而 System.ValueType 隐式继承于 System.Objec...

2019面试宝典之.Net

  1、简述 private、 protected、 public、 internal 修饰符的访问权限。 private : 私有成员, 在类的内部才可以访问。 protected : 保护成员,该类内部和继承类中可以访问。 public : 公共成员,完全公开,没有访问限制。 internal: 当前程序集内可以访问。 2、ADO.NET中的五个主要...