浅拷贝和深拷贝深入理解(shallow copy VS deep copy)

引言
C#中有两种类型变量,一种 是值类型变量,一种是引用类型变量,对于值类型变量,深拷贝和前拷贝都是通过赋值操作符号(=)实现,其效果一致,将对象中的值类型的字段拷贝到新的对象中.这个很容易理解。 本文重点讨论引用类型变量的拷贝机制和实现。

C#中引用类型对象的copy操作有两种:

•浅拷贝(影子克隆/shallow copy):只复制对象的值类型字段,对象的引用类型,仍属于原来的引用.
•深拷贝(深度克隆):不仅复制对象的值类型字段,同时也复制原对象中的对象.就是说完全是新对象产生的.

浅拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。

注意:string类型有点特殊,对于浅拷贝,类值类型对象进行处理。

浅拷贝的实现
1.使用Object类MemberwiseClone实现
MemberwiseClone:创建当前 Object 的浅表副本。
MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。

代码实现如下:


代码如下:

public class Person
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }

public object Clone()
        {
           return   this.MemberwiseClone();   
        }

}

public class Name
    {
        public Name(string frisName,string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

2.赋值操作(=)VS使用Object类MemberwiseClone实现
对于引用类型的变量,我们有种误解,认为赋值操作就是浅拷贝一种,其实不然,两者有区别。

1.浅拷贝(shallow copy)对于引用类型对象中的值类型字段进行了逐位复制。赋值运算符只是把源对象的引用赋值给目的对象,两者引用同一个对象。

2.浅拷贝后的对象的值类型字段更改不会反映到源对象,而赋值运算后的对象的值类型字段更改会反映到源对象

代码实现如下:


代码如下:

public class Person
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }
    }

public class Name
    {
        public Name(string frisName,string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

深拷贝实现
相对于浅拷贝,是指依照源对象为原型,创建一个新对象,将当前对象的所有字段进行执行逐位复制并支持递归,不管是是值类型还是引用类型,不管是静态字段还是非静态字段。
在C#中,我们们有三种方法实现深拷贝

1.实现ICloneable接口,自定义拷贝功能。

ICloneable 接口,支持克隆,即用与现有实例相同的值创建类的新实例。

ICloneable 接口包含一个成员 Clone,它用于支持除 MemberwiseClone 所提供的克隆之外的克隆。Clone 既可作为深层副本实现,也可作为浅表副本实现。在深层副本中,所有的对象都是重复的;而在浅表副本中,只有顶级对象是重复的,并且顶级以下的对象包含引用。 结果克隆必须与原始实例具有相同的类型或是原始实例的兼容类型。

代码实现如下:


代码如下:

public class Person:ICloneable
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }

public object Clone()
        {
            Person tem = new Person();
            tem.Address = this.Address;
            tem.Age = this.Age;

tem.Name = new Name(this.Name.FristName, this.Name.LastName);

return tem;
        }
    }

public class Name
    {
        public Name(string frisName, string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

大家可以看到,Person类继承了接口ICloneable并手动实现了其Clone方法,这是个简单的类,试想一下,如果你的类有成千上万个引用类型成员(当然太夸张,几十个还是有的),这是不是份很恐怖的劳力活?

2.序列化/反序列化类实现

不知道你有没有注意到DataSet对象,对于他提供的两个方法:

DataSet.Clone 方法,复制 DataSet 的结构,包括所有 DataTable 架构、关系和约束。不要复制任何数据。

新 DataSet,其架构与当前 DataSet 的架构相同,但是不包含任何数据。注意 如果已创建这些类的子类,则复本也将属于相同的子类。

DataSet.Copy 方法复制该 DataSet 的结构和数据.

新的 DataSet,具有与该 DataSet 相同的结构(表架构、关系和约束)和数据。注意如果已创建这些类的子类,则副本也将属于相同的子类。

好像既不是浅拷贝,又不是深拷贝,是不是很失望?但是两个结合起来不是我们要的深拷贝吗?看看DataSet的实现,注意序列化接口:ISerializable

序列化是将对象或对象图形转换为线性字节序列,以存储或传输到另一个位置的过程。反序列化是接受存储的信息并利用它重新创建对象的过程。

通过 ISerializable 接口,类可以执行其自己的序列化行为。

转换为线性字节序列后并利用其重新创建对象的过程是不是和我们的深拷贝的语意“逐位复制”很相像?

代码实现如下:


代码如下:

[Serializable]
    public class Person : ICloneable
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }

public object Clone()
        {
            using (MemoryStream ms = new MemoryStream(1000))
            {
                object CloneObject;

BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
                bf.Serialize(ms, this);

ms.Seek(0, SeekOrigin.Begin);

// 反序列化至另一个对象(即创建了一个原对象的深表副本)
                CloneObject = bf.Deserialize(ms);

// 关闭流
                ms.Close();
                return CloneObject;
            }
        }
    }

[Serializable]
    public class Name
    {
        public Name(string frisName, string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

}

注意:通过序列化和反序列化实现深拷贝,其和其字段类型必须标记为可序列化类型,既添加特性(Attribute)[Serializable]。

3.通过反射实现
通过序列化/反序列化方式我们能比较流畅的实现深拷贝,但是涉及到IO操作,托管的的环境中,IO操作比较消耗资源。 能不能有更优雅的解决方案。CreateInstance,对,利用反射特性。这个方法大家可以参考这篇博客:http://rubenhak.com/?p=70 文章反射类的Attribute,利用Activator.CreateInstance New一个类出来(有点像DataSet.Clone先获得架构),然后利用PropertyInfo的SetValue和GetValue方法,遍历的方式进行值填充。

代码实现如下:


代码如下:

public class Person
{
    private List<Person> _friends = new List<Person>();

public string Firstname { get; set; }
    public string Lastname { get; set; }

[Cloneable(CloneableState.Exclude)]
    [Cloneable(CloneableState.Include, "Friends")]
    public List<Person> Friends { get { return _friends; } }

[Cloneable(CloneableState.Exclude)]
    public PersonManager Manager { get; set; }
}

C#为什么要设计深拷贝和浅拷贝?
这个我也一直也找不到一个合适的答案,希望有人来讨论下!点击下载代码

(0)

相关推荐

  • JavaScript数组深拷贝和浅拷贝的两种方法

    例如这个例子: 复制代码 代码如下: var arr = ["One","Two","Three"]; var arrto = arr;arrto[1] = "test";document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,test,Threedocument.writeln("数组的新值

  • Java 深拷贝与浅拷贝的分析

    在正式的进入主题之前,我们先来了解下深拷贝和前拷贝的概念: 浅拷贝: 会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的是基本类型的值:如果属性是内存地址,拷贝的就是内存地址,因此如果一个对象改变了这个地址就会影响到另一个对象: 深拷贝: 不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值: 了解完概念之后,我们来测试下普通的对象赋值操作属于深拷贝还是浅拷贝: 测试代码: public class Depth

  • Java中的深拷贝(深复制)和浅拷贝(浅复制)介绍

    深拷贝(深复制)和浅拷贝(浅复制)是两个比较通用的概念,尤其在C++语言中,若不弄懂,则会在delete的时候出问题,但是我们在这幸好用的是Java.虽然java自动管理对象的回收,但对于深拷贝(深复制)和浅拷贝(浅复制),我们还是要给予足够的重视,因为有时这两个概念往往会给我们带来不小的困惑. 浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象.深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象.举例来说更加清楚:对象A1中包含对B1的引用

  • Python 拷贝对象(深拷贝deepcopy与浅拷贝copy)

    1. copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象.2. copy.deepcopy 深拷贝 拷贝对象及其子对象一个很好的例子: Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->import copya = [1, 2, 3, 4, ['a', 'b']]  #原始对象b = a  #赋值,传对象的引用c = copy.c

  • 浅析iOS中的浅拷贝和深拷贝(copy和mutableCopy)

    ios提供了copy和mutablecopy方法,顾名思义,copy就是复制了一个imutable的对象,而mutablecopy就是复制了一个mutable的对象. copy与retain的区别: copy是创建一个新对象,retain是创建一个指针,引用对象计数加1.Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化.copy减少对象对上下文的依赖. retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的

  • 简单谈谈C#中深拷贝、浅拷贝

    Object.MemberwiseClone 方法 创建当前 Object 的浅表副本. protected Object MemberwiseClone() MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象. 如果字段是值类型的,则对该字段执行逐位复制. 如果字段是引用类型,则复制引用但不复制引用的对象:因此,原始对象及其复本引用同一对象. 例如,考虑对象X引用对象 A 和 B , 对象 B 依次引用对象 C. X 的浅表副本

  • Java中的深拷贝和浅拷贝介绍

    一.引言   对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去.在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部 数据.Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy).深拷贝(Deep Copy).延迟拷贝(Lazy Copy). 二.浅拷贝 1.什么是浅拷贝   浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝.如果属性是基本类型,拷贝的就是基本类型的值:如果属性是内存地

  • C++拷贝构造函数(深拷贝与浅拷贝)详解

    对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. 复制代码 代码如下: #include <iostream>using namespace std; class CExample {private:    int a;public:    CExample(int b)    { a=b;}    void Show ()    {       

  • 浅拷贝和深拷贝深入理解(shallow copy VS deep copy)

    引言C#中有两种类型变量,一种 是值类型变量,一种是引用类型变量,对于值类型变量,深拷贝和前拷贝都是通过赋值操作符号(=)实现,其效果一致,将对象中的值类型的字段拷贝到新的对象中.这个很容易理解. 本文重点讨论引用类型变量的拷贝机制和实现. C#中引用类型对象的copy操作有两种: •浅拷贝(影子克隆/shallow copy):只复制对象的值类型字段,对象的引用类型,仍属于原来的引用.•深拷贝(深度克隆):不仅复制对象的值类型字段,同时也复制原对象中的对象.就是说完全是新对象产生的. 浅拷贝和

  • 深入理解python中的浅拷贝和深拷贝

    在讲什么是深浅拷贝之前,我们先来看这样一个现象: a = ['scolia', 123, [], ] b = a[:] b[2].append(666) print a print b 为什么我只对b进行修改,却影响到了a呢?看过我在之前的文章中就说过:序列中保存的都是内存的引用. 所以,当我们通过b去修改里面的空列表的时候,其实就是修改内存中的同一个对象,所以会影响到a. a = ['scolia', 123, [], ] b = a[:] print id(a), id(a[0]), id(

  • 浅谈Python浅拷贝、深拷贝及引用机制

    这礼拜碰到一些问题,然后意识到基础知识一段时间没巩固的话,还是有遗忘的部分,还是需要温习,这里做份笔记,记录一下 前续 先简单描述下碰到的题目,要求是写出2个print的结果 可以看到,a指向了一个列表list对象,在Python中,这样的赋值语句,其实内部含义是指a指向这个list所在内存地址,可以看作类似指针的概念. 而b,注意,他是把a对象包裹进一个list,并且乘以5,所以b的样子应该是一个大list,里面元素都是a 而当a对象进行了append操作后,其实,隐含的意思是,内存中的这个l

  • JavaScript基础心法 深浅拷贝(浅拷贝和深拷贝)

    前言 说到深浅拷贝,必须先提到的是JavaScript的数据类型,之前的一篇文章JavaScript基础心法--数据类型说的很清楚了,这里就不多说了. 需要知道的就是一点:JavaScript的数据类型分为基本数据类型和引用数据类型. 对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的. 浅拷贝 浅拷贝的意思就是只复制引用,而未复制真正的值. const originArray = [1,2,3,4,5]; const originObj = {a:'a'

  • javascript对浅拷贝和深拷贝的详解

    下面小编就为大家带来一篇浅谈JavaScript中面向对象的的深拷贝和浅拷贝.小编觉得挺不错的,现在就分享给大家,也给大家做个参考. 1.浅拷贝:复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据. 2.深拷贝(复杂):复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制. 这里画一个简单的图来加深理解: 一.数组的深浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个

  • 浅析javaScript中的浅拷贝和深拷贝

    1.javaScript的变量类型 (1)基本类型: 5种基本数据类型Undefined.Null.Boolean.Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问. (2)引用类型: 存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置.当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据. JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一

  • Python中的赋值、浅拷贝、深拷贝介绍

    和很多语言一样,Python中也分为简单赋值.浅拷贝.深拷贝这几种"拷贝"方式. 在学习过程中,一开始对浅拷贝理解很模糊.不过经过一系列的实验后,我发现对这三者的概念有了进一步的了解. 一.赋值 赋值算是这三种操作中最常见的了,我们通过一些例子来分析下赋值操作: str例 复制代码 代码如下: >>> a = 'hello' >>> b = 'hello' >>> c = a >>> [id(x) for x in

  • 详解Python核心编程中的浅拷贝与深拷贝

    一.问题引出浅拷贝 首先看下面代码的执行情况: a = [1, 2, 3] print('a = %s' % a) # a = [1, 2, 3] b = a print('b = %s' % b) # b = [1, 2, 3] a.append(4) # 对a进行修改 print('a = %s' % a) # a = [1, 2, 3, 4] print('b = %s' % b) # b = [1, 2, 3, 4] b.append(5) # 对b进行修改 print('a = %s'

  • JavaScript实现浅拷贝与深拷贝的方法分析

    本文实例讲述了JavaScript实现浅拷贝与深拷贝的方法.分享给大家供大家参考,具体如下: 平时使用数组复制时,我们大多数会使用'=',这只是浅拷贝,存在很多问题.比如 let arr = [1,2,3,4,5]; let arr2 = arr; console.log(arr) //[1, 2, 3, 4, 5] console.log(arr2) //[1, 2, 3, 4, 5] arr[0] = 6; console.log(arr) //[6, 2, 3, 4, 5] console

随机推荐