C#中深拷贝和浅拷贝的介绍与用法
作者:.NET开发菜鸟
一、什么是深拷贝和浅拷贝
对于所有面向对象的语言,复制永远是一个容易引发讨论的题目,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#中深拷贝和浅拷贝的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。