C# 深拷贝和浅拷贝
2017-10-27 15:13 糯米粥 阅读(1249) 评论(0) 编辑 收藏 举报在编码中。经常会遇到赋值操作。值类型就不说了。如果是引用类型赋值。其实是引用传递,即赋值的是一个引用。比如:
Person p1 = new Person("张三", "北京"); Person p2 = p1; p2.name = "李四"; //此时,p1.name和p2.name 都被改变为 李四
当把p1赋值给p2时,那么p1和p2指向同一个地址。所有当改变p2对象的name的时候,会直接影响到p1的name
这就是值类型和引用类型的区别
那么这个跟深拷贝和浅拷贝有什么区别呢?
首先看看深拷贝(Deep Copy)和浅拷贝(Shallow Copy)的区别。(知识来源于网络)
|
基本类型属性 | 引用类型属性 | 引用类型备注 |
浅拷贝 | 拷贝值 | 拷贝引用,指向原引用的地址 | 如果修改引用的属性,都会影响另外一个对像 |
深拷贝 | 拷贝值 | 拷贝引用和引用的内容,并创建新的实例,指向新的地址 | 可以理解,创建一个新的对象,把原对象的内容复制到新对象中 |
深拷贝:
指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人叫张三,然后使用克隆技术以张三来克隆另外一个人叫李四,这样张三和李四就是相互独立的,不管张三缺胳膊还是李四少腿了都不会影响另外一个人
浅拷贝:
指的是拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。此时,其中一个对象的改变都会影响到另一个对象。例如,一个人一开始叫张三,后来改名字为张老三了,可是他们还是同一个人,不管张三缺胳膊还是张老三少腿,都反应在同一个人身上。在.NET中引用类型就是一个例子。
我们来改造下Person类,加个Clone 方法
public class Person { public string name { get; set; } public string address { get; set; } public Person(string name, string address) { this.name = name; this.address = address; } public object Clone() { return this.MemberwiseClone(); } }
F12看看MemberwiseClone的解释
测试代码,ok,通过浅拷贝的方式。发现。改变p2.name。p1是不受影响的,有人会问。上面的那个表格
明明说了:如果修改引用的属性,都会影响另外一个对像, 稍后再看
既然有浅拷贝,那么就有深拷贝,不过C#并没有像提供MemberwiseClone()方法一样提供一个深拷贝的方法。
深拷贝。一般是通过反序列化或者反射。或者直接new一个新的对象,new一个新的对象。简单 Person p3 = new Person();
看看反序列化的深拷贝,在person类添加深拷贝方法
/// <summary> /// 深拷贝 /// </summary> /// <returns></returns> public object DeepCopy() { BinaryFormatter bFormatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); bFormatter.Serialize(stream, this); stream.Seek(0, SeekOrigin.Begin); return (Person)bFormatter.Deserialize(stream); }
然后修改测试代码, Person p2 = (Person)p1.DeepCopy(); 运行。发现有错误,标记类为可序列化即可 [Serializable]关键字
好了。测试看看效果
通过上面的 浅拷贝,和深拷贝结果对比。是不是发现结果一样。改变p2的值不会影响到p1的值。只是颜色变了而已(其实这里颜色变化是有玄机的)
刚开始。我也有点迷惑。其实深拷贝和浅拷贝之间的区别在于是否复制了子对象。
上面的Person类的两个属性:name和address都是string。大家知道。string在C#中是特殊的引用类型。它的值是只读的
所以在上面的测试中用了浅拷贝和深拷贝,然后改变p2的值也不会影响p1的值。不知道我这样说是否说明白了
所以我为了了测试。在person中加个引用类型的变量。也就是类类型 Room
那么既然是子对象(这里说的子对象,其实也就是说类的属性是一个对象)。就在person中加个子对象。创建一个新的Room类
public class Room { public int roomId { get; set; } public string roomName { get; set; } public Room(int rId,string rName) { this.roomId = rId; this.roomName = rName; } }
在person中新增字段和构造函数
public Room room { get; set; } public Person(string name, string address, Room room) { this.name = name; this.address = address; this.room = room; }
先用Clone() 方法。Clone方法是浅拷贝
上面是浅拷贝 当拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。
此时,其中一个对象的改变都会影响到另一个对象,因为p1和p2的Room都指向同一个实体,当改变p1的roomId时候
直接影响到了p2的roomId。
下面用一个图来说明浅拷贝原理。(注:图片来源于网络)
然后用 DeepCopy() 方法 DeepCopy 是 深拷贝
上面是浅拷贝 当拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。
这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响
所以。就算你怎么改变p1中roomId的值,p2 的roomId也不会受影响
下面用一个图来说明深拷贝原理。(注:图片来源于网络)
浅拷贝与赋值操作
大多数面向对象语言中的赋值操作都是传递引用,即改变对象的指针地址,而并没有复制内存,
也没有做任何复制操作。由此可知,浅拷贝与赋值操作的区别是顶级对象的复制与否。
当然,也有一些例外情况,比如类型定义中重载赋值操作符(assignment operator),
或者某些类型约定按值传递,就像C#中的结构体和枚举类型。
网络供图
拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,
其中任何一个对象的改动都不会对另外一个对象造成影响
仔细对比下。两个截图的结果。不难发现。
深拷贝时两个对象是完全“分离”的,改变其中一个,不会影响到另一个对象;浅拷贝时两个对象并未完全“分离”,改变顶级对象的内容,
不会对另一个对象产生 影响,但改变子对象的内容,则两个对象同时被改变。这种差异的产生,即是取决于拷贝子对象时复制内存还是复制指针。
深拷贝为子对象重新分配了一段内存空 间,并复制其中的内容;浅拷贝仅仅将指针指向原来的子对象。
参考资料:
http://www.cnblogs.com/qingteng1983/archive/2010/10/10/1847511.html
http://frankxulei.blog.51cto.com/1596834/318538/
http://www.cnblogs.com/liuconglin/p/6411259.html
http://www.cnblogs.com/ysyn/archive/2013/12/25/3490124.html
www.cnblogs.com/tinya/p/4511120.html
http://www.cnblogs.com/zhili/p/DeepCopy.html
dapper
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
2013-10-27 C#读取Excel,Access数据库