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

 更新时间:2022年03月21日 09:16:51   作者:.NET开发菜鸟  
本文详细讲解了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#中深拷贝和浅拷贝的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • C#实现将一个字符串进行翻转显示的6种方法

    C#实现将一个字符串进行翻转显示的6种方法

    下面小编就为大家分享一篇C#实现将一个字符串进行翻转显示的6种方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • C#获取指定文件著作权信息的方法

    C#获取指定文件著作权信息的方法

    这篇文章主要介绍了C#获取指定文件著作权信息的方法,涉及C#中FileVersionInfo类的使用技巧,需要的朋友可以参考下
    2015-04-04
  • 通过C#实现裁剪PDF页面功能

    通过C#实现裁剪PDF页面功能

    在处理PDF文档时,有时需要精确地裁剪页面以适应特定需求,比如去除广告、背景信息或者仅仅是为了简化文档内容,本文将指导如何使用免费.NET控件通过C#实现裁剪PDF页面,需要的朋友可以参考下
    2024-09-09
  • Unity常用命令模式详解

    Unity常用命令模式详解

    这篇文章主要为大家详细介绍了Unity常用命令模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • asp.net core 使用 tensorflowjs实现 face recognition的源代码

    asp.net core 使用 tensorflowjs实现 face recognition的源代码

    tensorflowjs,在该项目中使用了ml5js这个封装过的机器学习JavaScript类库, 使用起来更简单,本文给大家分享asp.net core 使用 tensorflowjs实现 face recognition的源代码,需要的朋友参考下吧
    2021-06-06
  • C#对Access进行增删改查的完整示例

    C#对Access进行增删改查的完整示例

    本文主要是讲C#对Access数据库的增删改查操作,想学习C#和Access数据库操作基础的可以参考借鉴,以下代码都经过实践测试可用,下面跟着小编一起来看看。
    2016-08-08
  • C#实现MQTT服务端与客户端通讯功能

    C#实现MQTT服务端与客户端通讯功能

    这篇文章介绍了C#实现MQTT服务端与客户端通讯的功能,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • C# 设计模式系列教程-命令模式

    C# 设计模式系列教程-命令模式

    在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
    2016-06-06
  • C#简单读取主机上所有进程的方法

    C#简单读取主机上所有进程的方法

    这篇文章主要介绍了C#简单读取主机上所有进程的方法,涉及C#进程的遍历读取操作相关实现技巧,需要的朋友可以参考下
    2016-08-08
  • 详解WPF如何动态生成DataGrid的行和列

    详解WPF如何动态生成DataGrid的行和列

    在日常开发中,DataGrid作为二维表格,非常适合数据的展示和统计,本文以一些简单的小例子,简述在WPF开发中,如何动态生成DataGrid的行和列,需要的可以了解下
    2024-02-02

最新评论