C#中深拷贝和浅拷贝的介绍与用法

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

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

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

深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。比如一个黄狗叫大黄,使用克隆术克隆另外一个黄狗叫小黄,这样大黄和小黄就相对独立了,他们不互相影响。在.NET中int,double以及结构体和枚举等。

int a=12;
int c=a;//进行了深拷贝
c=232 //不影响

浅拷贝:指的是拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。此时,其中一个对象的改变都会影响到另一个对象。就像一个人改名了一样,他还是这个人,只不过名字变了而已。

public class YDog
    {
        public string Name { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            YDog sourceP = new YDog() { Name = "大黄" };
            YDog copyP = sourceP; // 浅拷贝
            copyP.Name = "小黄"; // 拷贝对象改变Name值
            // 结果都是"小黄",因为实现的是浅拷贝,一个对象的改变都会影响到另一个对象
            Console.WriteLine("YDog.Name: [SourceP: {0}] [CopyP:{1}]", sourceP.Name, copyP.Name);
            Console.Read();
        }
    }

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

浅拷贝示意图如下所示:

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

类型基类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#中的深拷贝通过ICloneable接口来实现。这句话并不正确。事实上任何名字的方法都可以用来实现深拷贝,并且没有任何语法来规定深拷贝只能通过Clone方法来实现。Clone这个名字只是一种习惯的称呼,而实现ICloneable只能带来一般接口的通用便利性,而并没有任何关于拷贝的特殊性。

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

实现深拷贝

1、新建一个对象,一个一个的重新赋值,麻烦一点

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ServiceTest
{
    public class Program
    {
        static void Main(string[] args)
        {

            YDog Dog = new YDog() { Name = "大黄" };
            YDog NewDog = new YDog();
            NewDog.Name = Dog.Name;

            Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
            Console.Read();
        }
    }
}

输出结果

2、利用反射实现深拷贝

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ServiceTest
{
    public class Program
    {
        static void Main(string[] args)
        {

            YDog Dog = new YDog() { Name = "大黄" };
            YDog NewDog = (YDog)DeepCopy(Dog);
            NewDog.Name = Dog.Name;

            Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
            Console.Read();
        }

        /* 利用反射实现深拷贝*/
        public static object DeepCopy(object _object)
        {
            Type T = _object.GetType();
            object o = Activator.CreateInstance(T);
            PropertyInfo[] PI = T.GetProperties();
            for (int i = 0; i < PI.Length; i++)
            {
                PropertyInfo P = PI[i];
                P.SetValue(o, P.GetValue(_object));
            }
            return o;
        }
    }
}

输出结果

3、利用序列化和反序列化来实现,如下代码

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace ServiceTest
{
    public class Program
    {
        static void Main(string[] args)
        {

            YDog Dog = new YDog() { Name = "大黄" };
            //YDog NewDog = (YDog)DeepCopy(Dog);
            //NewDog.Name = Dog.Name;

            // 序列化实现
            YDog NewDog = (YDog)DeepCopy<YDog>(Dog);

            Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
            Console.Read();
        }

        /* 利用反射实现深拷贝*/
        public static object DeepCopy(object _object)
        {
            Type T = _object.GetType();
            object o = Activator.CreateInstance(T);
            PropertyInfo[] PI = T.GetProperties();
            for (int i = 0; i < PI.Length; i++)
            {
                PropertyInfo P = PI[i];
                P.SetValue(o, P.GetValue(_object));
            }
            return o;
        }

        // 利用XML序列化和反序列化实现
        public static T DeepCopyWithXmlSerializer<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(T));
                xml.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                retval = xml.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        // 利用二进制序列化和反序列实现
        public static T DeepCopyWithBinarySerialize<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                // 序列化成流
                bf.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                // 反序列化成对象
                retval = bf.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        public static T DeepCopy<T>(T obj)
        {
            // 序列化
           string json= JsonConvert.SerializeObject(obj);
            // 反序列化
           return JsonConvert.DeserializeObject<T>(json);
        }
    }
}

二、总结

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

到此这篇关于C#中深拷贝和浅拷贝的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • c# 深拷贝与浅拷贝的区别分析及实例

    深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响.举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人.比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等. 浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同).对其中任何一个对象的改动都会影响另外一个对象.举个例子,一个人一开始叫张三,后来

  • C#深浅拷贝的深入解析

    前言 前面我们学习完了设计模式,在其中我们有了解到原型模式.这里涉及到了克隆自身对象.那么也就是对对象进行拷贝.这里就涉及到了这么一个概念.深浅拷贝.何为深拷贝何为浅拷贝呢?我们一起来看看吧. 浅拷贝 首先我们看看浅拷贝.浅拷贝就是将对象中的所有字段复制到新对象中去,浅拷贝对于值类型和引用类型有不同的影响.值类型的值被复制到副本中后,修改副本中的值不会影响原来对象的值.然而引用类型被复制到副本中的是引用类型的引用.不是引用的对象.这样再修改副本中的值是会导致原来对象的值也被修改了.但是这里引用类

  • C#浅拷贝和深拷贝实例解析

    在有些时候,我们需要从数据库读取数据填充对象或从硬盘读取文件填充对象,但是这样做相对耗时.这时候我们就想到了对象的拷贝.本文即以实例形式解析了C#浅拷贝和深拷贝的用法.具体如下: 一.浅拷贝 1.什么是"浅拷贝": 当针对一个对象前拷贝的时候,对于对象的值类型成员,会复制其本身,对于对象的引用类型成员,仅仅复制对象引用,这个引用指向托管堆上的对象实例. 2.有一个对象,包含引用类型的类成员和值类型的struct成员 Cinema包含引用类型成员Room和值类型成员Film. publi

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

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

  • C#中深拷贝和浅拷贝的介绍与用法

    一.什么是深拷贝和浅拷贝 对于所有面向对象的语言,复制永远是一个容易引发讨论的题目,C#中也不例外.此类问题在面试中极其容易被问到,我们应该在了解浅拷贝和深拷贝基本概念的基础上,从设计的角度进一步考虑如何支持对象的拷贝. 在System.Object类中,有一个受保护的方法object.MemberwiseClone(),这个方法实现了对象的复制.事实上,它所实现的就是我们所称的浅拷贝. 深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝.这样进行深拷贝后的拷

  • Python中深拷贝与浅拷贝的区别介绍

    首先,我们知道 Python 中有6个标准的数据类型,他们又分为可以变和不可变.不可变:Number(数字).String(字符串).Tuple(元组).可以变:List(列表).Dictionary(字典).Set(集合). 浅拷贝 改变原始对象中为可变类型的元素的值,会同时影响拷贝对象.改变原始对象中为不可变类型的元素的值,不会响拷贝对象. 代码演示 import copy #定义一个列表,其中第一个元素是可变类型. list1 = [[1,2], 'fei', 66]; #进行浅copy

  • JavaScript中深拷贝与浅拷贝详解

    目录 1 浅拷贝概念 2 深拷贝概念 3 浅拷贝的实现方式 3.1 Object.assign() 3.2 Array.prototype.concat() 3.3 Array.prototype.slice() 3.4 直接赋值 4 深拷贝的实现方式 4.1 JSON.parse(JSON.stringify()) 4.2 函数库lodash 总结 1 浅拷贝概念 深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的. 浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份精确拷

  • PHP中register_shutdown_function函数的基础介绍与用法详解

    前言 最近在看<PHP核心技术与最佳实践>,里面有使用到一个函数,register_shutdown_function,由于之前没有用过该函数,就去查了一下资料,就觉得是个很实用的函数,所以这里写一下这个函数的用法.下面话不多说了,来一起看看详细的介绍吧. 1. 函数说明 定义:该函数是来注册一个会在PHP中止时执行的函数 参数说明: void register_shutdown_function ( callable $callback [, mixed $parameter [, mixe

  • node.js中express中间件body-parser的介绍与用法详解

    前言 Node中的核心模块分两类:一类是自带的核心模块,如http.tcp等,第二类是第三方核心模块,express就是与http对应的第三方核心模块,用于处理http请求.express在3.0版本中自带有很多中间件,但是在express 4.0以后,就将除static(静态文件处理)以外的其他中间件分离出来了:在4.0以后需要使用中间件时,就需要单独安装好相应的中间件以后调用,以下3.0与4.0中间件的中间件区别(3.0是内置中间件属性名,4.0是需要安装的中间件名称): Express 3

  • 一文带你搞懂Numpy中的深拷贝和浅拷贝

    目录 1. 引言 2. 浅拷贝 2.1 问题引入 2.2 问题剖析 3. 深拷贝 3.1 举个栗子 3.2 探究原因 4. 技巧总结 4.1 判断是否指向同一内存 4.2 其他数据类型 5. 总结 1. 引言 深拷贝和浅拷贝是Python中重要的概念,本文重点介绍在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理. 闲话少说,我们直接开始吧! 2. 浅拷贝 2.1 问题引入 我们来举个栗子,如下所示我们有两个数组a和b,样例代码如下: import numpy as np a = np.ar

  • Python中Numpy的深拷贝和浅拷贝

    目录 1. 引言 2. 浅拷贝 2.1 问题引入 2.2 问题剖析 3. 深拷贝 3.1 举个栗子 3.2 探究原因 4. 技巧总结 4.1 判断是否指向同一内存 4.2 其他数据类型 5. 总结 1. 引言 深拷贝和浅拷贝是Python中重要的概念,本文重点介绍在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理.闲话少说,我们直接开始吧! 2. 浅拷贝 2.1 问题引入 我们来举个栗子,如下所示我们有两个数组a和b,样例代码如下: import numpy as np a = np.arr

  • 深入浅析Python中list的复制及深拷贝与浅拷贝

    在Python中,经常要对一个list进行复制.对于复制,自然的就有深拷贝与浅拷贝问题.深拷贝与浅拷贝的区别在于,当从原本的list复制出新的list之后,修改其中的任意一个是否会对另一个造成影响,即这两个list在内存中是否储存在同一个区域,这也是区分深拷贝与浅拷贝的重要依据.接下来我们就针对Python中list复制的几种方法,来探究一下其是属于深拷贝还是浅拷贝.弄清楚这个问题,有助于我们在编程中规避错误,减少不必要的调试时间. 一.非拷贝方法--直接赋值 如果用=直接赋值,是非拷贝方法.这

  • 详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    1.序列化实现 public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object){ T cloneObj = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new Objec

随机推荐