对象属性和字段拷贝的几种方式

微软提供了浅拷贝

  • 对于值类型,修改拷贝的值不会影响源对象
  • 对于引用类型,修改拷贝后的值会影响源对象,但string特殊,它会拷贝一个副本,互相不会影响

自己实现深拷贝,我了解到的有这几种方法

  1. 硬核编码,每一个属性和字段都写一遍赋值,这种方法运行速度最快
  2. 通过反射,最常见的方法,但每次都需要反射
  3. 通过序列化,需要给类加上[Serializable]标签
  4. C# 快速高效率复制对象另一种方式 表达式树

测试例子

例子代码在文章未尾,这里先展示测试结果。

最开始创建对象的字段值为: Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士

1.原始值变化后,使用深浅两种拷贝的结果

  1. //原始值:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士,
  2. //修改原始值的Id和Name,Skin字段之后,输出如下:
  3. //原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:隐刃,
  4. //浅拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:隐刃,
  5. //深拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士

2.修改浅拷贝的值,再打印看看结果

  1. //输出:修改浅拷贝的Id,Name,Prof,Skin,输出如下:
  2. //原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:隐刃,
  3. //浅拷贝:Id:1008,Name:李白,Hp:3449,Prof:刺客,Skin:凤求凰,
  4. //深拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士

㳀拷贝

MemberwiseClone

Object.MemberwiseClone函数定义:

  1. /// <summary>
  2. /// 创建当前 <see cref="T:System.Object" /> 的浅表副本。
  3. /// </summary>
  4. /// <returns>
  5. /// 当前 <see cref="T:System.Object" /> 的浅表副本。
  6. /// </returns>
  7. protected extern object MemberwiseClone();

结论

MemberwiseClone理论上满足常见的需求,包括string这种特殊类型,拷贝后的副本与原始值是断开联系,修改不会相互影响。

反射对于List、Hashtable等复杂结构需要特殊处理

例子

  1. [Serializable]
  2. class XEngine : ICloneable
  3. {
  4. public object Clone()
  5. {
  6. return this.MemberwiseClone();
  7. }
  8. }

深拷贝

比较常见的就是通过反射对所有字段和属性进行赋值,还可以通过序列化也是可以对所有字段和属性赋值。

序列化

  1. public XEngine DeepClone()
  2. {
  3. using (Stream objectStream = new MemoryStream())
  4. {
  5. IFormatter formatter = new BinaryFormatter();
  6. formatter.Serialize(objectStream, this);
  7. objectStream.Seek(0, SeekOrigin.Begin);
  8. return formatter.Deserialize(objectStream) as XEngine;
  9. }
  10. }

反射拷贝

反射所有的属性和字段,进行赋值,但对于hashtable和list等复杂结构是不好处理的。

  1. public void ReflectClone(object from, object to)
  2. {
  3. if (from == null || to == null)
  4. {
  5. Debug.LogError($"拷贝失败,from is null:{from == null},to is null:{to == null}");
  6. return;
  7. }
  8. var fromType = from.GetType();
  9. var toType = to.GetType();
  10. //拷贝属性
  11. var properties = fromType.GetProperties();
  12. foreach (PropertyInfo prop in properties)
  13. {
  14. var toProp = toType.GetProperty(prop.Name);
  15. if (toProp != null)
  16. {
  17. var val = prop.GetValue(from);
  18. if (prop.PropertyType == toProp.PropertyType)
  19. {
  20. toProp.SetValue(to, val, null);
  21. }
  22. else if (prop.PropertyType.ToString().IndexOf("List") >= 0 || prop.PropertyType.ToString().IndexOf("Hashtable") >= 0)
  23. {
  24. Debug.LogError($"属性:{prop.Name},不支持List和Hashtable的拷贝,请使用序列化");
  25. }
  26. }
  27. }
  28. //拷贝字段
  29. var fields = fromType.GetFields();
  30. foreach (FieldInfo field in fields)
  31. {
  32. var toField = toType.GetField(field.Name);
  33. if (toField != null)
  34. {
  35. var val = field.GetValue(from);
  36. if (field.FieldType == toField.FieldType)
  37. {
  38. toField.SetValue(to, val);
  39. }
  40. else if (field.FieldType.ToString().IndexOf("List") >= 0 || field.FieldType.ToString().IndexOf("Hashtable") >= 0)
  41. {
  42. Debug.LogError($"字段:{field.Name},不支持List和Hashtable的拷贝,请使用序列化");
  43. }
  44. }
  45. }
  46. }

在Unity中的例子

unity引擎版本:2019.3.7f1,完整代码如下:

  1. using System;
  2. using System.IO;
  3. using System.Reflection;
  4. using System.Runtime.Serialization;
  5. using System.Runtime.Serialization.Formatters.Binary;
  6. using UnityEngine;
  7. using Object = System.Object;
  8. /// <summary>
  9. /// Author:qingqing.zhao (569032731@qq.com)
  10. /// Date:2021/5/18 10:54
  11. /// Desc:在Unity中测试几种对象拷贝的方法
  12. /// 1.微软提供的浅拷贝
  13. /// 2.序列化
  14. /// 3.反射拷贝
  15. ///结论:int,bool等值类型和string浅拷贝之后修改原始值不会影响clone值,但引用类型会影响
  16. /// </summary>
  17. public class CloneDemo : MonoBehaviour
  18. {
  19. private void Start()
  20. {
  21. #region 例子1
  22. //测试修改一个只有基础数据结构的类,结论:int和string浅拷贝之后修改源始值不会影响clone值
  23. XCharacter role = new XCharacter() {Id = 1001, Name = "亚瑟", Hp = 3449, Prof = "战士", Skin = new XSkin() {Name = "死亡骑士"}};
  24. Debug.Log($"原始值:{role.ToString()}");
  25. XCharacter simpleClone = role.Clone() as XCharacter;
  26. XCharacter deepClone = role.DeepClone();
  27. role.Id = 1005;
  28. role.Name = "兰陵王";
  29. role.Prof = "刺客";
  30. role.Skin.Name = "影刃";
  31. Debug.Log($"修改原始值,原始值:{role.ToString()},浅拷贝:{simpleClone.ToString()},深拷贝:{deepClone.ToString()}");
  32. //输出:修改原始值,
  33. //原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃,
  34. //浅拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:影刃,
  35. //深拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士
  36. simpleClone.Id = 1008;
  37. simpleClone.Prof = "刺客";
  38. simpleClone.Name = "李白";
  39. Debug.Log($"修改浅拷贝的值,原始值:{role.ToString()},浅拷贝:{simpleClone.ToString()},深拷贝:{deepClone.ToString()}");
  40. //输出:修改浅拷贝的值,
  41. //原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃,
  42. //浅拷贝:Id:1008,Name:李白,Hp:3449,Prof:刺客,Skin:影刃,
  43. //深拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士
  44. #endregion
  45. #region 通过反射拷贝
  46. XCharacter reflectClone = new XCharacter();
  47. ReflectClone(role, reflectClone);
  48. Debug.Log($"反射拷贝,原始值:{role.ToString()},反射拷贝:{reflectClone.ToString()}");
  49. //输出:反射拷贝,
  50. //原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃,
  51. //反射拷贝:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃
  52. #endregion
  53. }
  54. }
  55. [Serializable]
  56. class XCharacter : ICloneable
  57. {
  58. public int Id { get; set; }
  59. public string Name { get; set; }
  60. public int Hp;
  61. public string Prof;
  62. public XSkin Skin { get; set; }
  63. public override string ToString()
  64. {
  65. return $"Id:{Id},Name:{Name},Hp:{Hp},Prof:{Prof},Skin:{Skin?.ToString()}";
  66. }
  67. public object Clone()
  68. {
  69. return this.MemberwiseClone();
  70. }
  71. public XCharacter DeepClone()
  72. {
  73. using (Stream objectStream = new MemoryStream())
  74. {
  75. IFormatter formatter = new BinaryFormatter();
  76. formatter.Serialize(objectStream, this);
  77. objectStream.Seek(0, SeekOrigin.Begin);
  78. return formatter.Deserialize(objectStream) as XCharacter;
  79. }
  80. }
  81. }
  82. [Serializable]
  83. class XSkin
  84. {
  85. public string Name { get; set; }
  86. public override string ToString()
  87. {
  88. return this.Name;
  89. }
  90. }

C#对象属性浅拷贝和深拷贝的更多相关文章

  1. [编程基础] Python对象的浅拷贝与深拷贝笔记

    Python中的赋值语句不创建对象的副本,它们只将名称绑定到对象.对于不可变的对象,这通常没有什么区别.但是对于处理可变对象或可变对象的集合,您可能需要寻找一种方法来创建这些对象的"真实副本 ...

  2. Java对象的浅拷贝和深拷贝&&String类型的赋值

    Java中的数据类型分为基本数据类型和引用数据类型.对于这两种数据类型,在进行赋值操作.方法传参或返回值时,会有值传递和引用(地址)传递的差别. 浅拷贝(Shallow Copy): ①对于数据类型是 ...

  3. JS数组和对象的浅拷贝和深拷贝

    共勉~ 在许多编程语言中,传递参数和赋值是通过值的直接复制或者引用复制完成的.在JavaScript中,对于值是直接进行复制还是引用复制在语法上是没有区别的,完全是根据值的类型来决定的. 在JavaS ...

  4. Python中的可变对象与不可变对象、浅拷贝与深拷贝

    Python中的对象分为可变与不可变,有必要了解一下,这会影响到python对象的赋值与拷贝.而拷贝也有深浅之别. 不可变对象 简单说就是某个对象存放在内存中,这块内存中的值是不能改变的,变量指向这块 ...

  5. js对象的浅拷贝与深拷贝

    浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,原始(基本)类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,ob ...

  6. Object对象的浅拷贝与深拷贝方法详解

    /* ===================== 直接看代码 ===================== */ <!DOCTYPE html> <html> <head& ...

  7. js中值的基本类型与引用类型,以及对象引用,对象的浅拷贝与深拷贝

    js有两种类型的值:栈:原始数据类型(undefinen,null,boolead,number,string)堆:引用数据类型(对象,函数和数组)两种类型的区别是:储存位置不同,原始数据类型直接存储 ...

  8. 面试题常考&必考之--js中的对象的浅拷贝和深拷贝(克隆,复制)(下)

    这里主要是讲深拷贝: 深拷贝:个人理解就是拷贝所有的层级 1.像对象里再放数组和对象这些叫引用值.开始我们先判断大对象中是否有引用值(数组和小对象), 然后在判断引用值是数组还是对象 2.开始啦: 1 ...

  9. [转] js对象浅拷贝和深拷贝详解

    本文为大家分享了JavaScript对象的浅拷贝和深拷贝代码,供大家参考,具体内容如下 1.浅拷贝 拷贝就是把父对像的属性,全部拷贝给子对象. 下面这个函数,就是在做拷贝: var Chinese = ...

  10. js对象浅拷贝和深拷贝详解

    js对象浅拷贝和深拷贝详解 作者:i10630226 字体:[增加 减小] 类型:转载 时间:2016-09-05我要评论 这篇文章主要为大家详细介绍了JavaScript对象的浅拷贝和深拷贝代码,具 ...

随机推荐

  1. Spring 太肥、太慢?你受不了?那 Solon Java Framework 就是你的西施

    Solon 是什么? Java 生态型应用开发框架.它从零开始构建,有自己的标准规范与开放生态(历时五年,已有全球第二级别的生态规模).与其他框架相比,它解决了两个重要的痛点:启动慢,费内存.2023 ...

  2. ByteBuffer 字节缓冲区

          HeapByteBuffer 在jvm堆上面的一个buffer,底层的本质是一个数组  由于内容维护在jvm里,所以把内容写进buffer里速度会快些:并且,可以更容易回收 DirectB ...

  3. Java实现压缩文件浅谈

    背景: 在Java中,可以使用java.util.zip包提供的类来进行文件的压缩和解压缩操作.主要涉及的类有ZipOutputStream.ZipEntry.ZipInputStream和Infla ...

  4. 3-3 vector 和 迭代器

    1 vector 容器vector可以理解为变长数组,它里面放的是相同类型的元素. vector<int> vec={1,2,3,4};//拷贝构造 vector<string> ...

  5. 七、手动制作docker镜像

    系列导航 一.docker入门(概念) 二.docker的安装和镜像管理 三.docker容器的常用命令 四.容器的网络访问 五.容器端口转发 六.docker数据卷 七.手动制作docker镜像 八 ...

  6. Redhat5 和Redhat6安装oracle11g

    oralce安装本人认为最麻烦的就是oracle包的版本和oracle的依赖的包的问题,这个做不好后期安装过程就会出现很多诡异的问题,这里总结一下Redhat5 和Redhat6安装oracle11g ...

  7. vue学习笔记 五、创建子组件实例

    系列导航 vue学习笔记 一.环境搭建 vue学习笔记 二.环境搭建+项目创建 vue学习笔记 三.文件和目录结构 vue学习笔记 四.定义组件(组件基本结构) vue学习笔记 五.创建子组件实例 v ...

  8. 双非本科拿下oppo sp!这位粉丝太强了!

    哈喽,大家好,我是仲一.今天分享的是一位双非本科生拿下oppo sp的秋招经验.当时,这位粉丝咨询我offer选择的时候,看到年薪31W这个数字,我以为他是研究生.后来,再三确认了,他确实是本科生. ...

  9. Vue tinymce富文本编辑器整合

    最近再弄一个后台管理系统,挑选了不少的编辑器,最终选择了tinymce,UI精美,功能模块多,可按需加载配置 vue cli 3 + tinymce5.0版本整合参考:https://liubing. ...

  10. [工程开发]当我们写一个tcp服务端的时候,我们在写什么?(一)

    当我们写一个tcp服务器和客户端的时候,我们在写什么?(一) 本篇只聊服务端. 最近想搞一个服务器的协议,然后捏,简单搓个tcp服务器协议看看效果,主要是最近实在是没事干,闲得没事搓个服务器看看,当然 ...