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

微软提供了浅拷贝

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

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

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

测试例子

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

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

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

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

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

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

㳀拷贝

MemberwiseClone

Object.MemberwiseClone函数定义:

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

结论

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

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

例子

[Serializable]
class XEngine : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}

深拷贝

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

序列化

public XEngine DeepClone()
{
using (Stream objectStream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectStream, this);
objectStream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(objectStream) as XEngine;
}
}

反射拷贝

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

public void ReflectClone(object from, object to)
{
if (from == null || to == null)
{
Debug.LogError($"拷贝失败,from is null:{from == null},to is null:{to == null}");
return;
} var fromType = from.GetType();
var toType = to.GetType();
//拷贝属性
var properties = fromType.GetProperties();
foreach (PropertyInfo prop in properties)
{
var toProp = toType.GetProperty(prop.Name);
if (toProp != null)
{
var val = prop.GetValue(from);
if (prop.PropertyType == toProp.PropertyType)
{
toProp.SetValue(to, val, null);
}
else if (prop.PropertyType.ToString().IndexOf("List") >= 0 || prop.PropertyType.ToString().IndexOf("Hashtable") >= 0)
{
Debug.LogError($"属性:{prop.Name},不支持List和Hashtable的拷贝,请使用序列化");
}
}
} //拷贝字段
var fields = fromType.GetFields();
foreach (FieldInfo field in fields)
{
var toField = toType.GetField(field.Name);
if (toField != null)
{
var val = field.GetValue(from);
if (field.FieldType == toField.FieldType)
{
toField.SetValue(to, val);
}
else if (field.FieldType.ToString().IndexOf("List") >= 0 || field.FieldType.ToString().IndexOf("Hashtable") >= 0)
{
Debug.LogError($"字段:{field.Name},不支持List和Hashtable的拷贝,请使用序列化");
}
}
}
}

在Unity中的例子

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

using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using Object = System.Object; /// <summary>
/// Author:qingqing.zhao (569032731@qq.com)
/// Date:2021/5/18 10:54
/// Desc:在Unity中测试几种对象拷贝的方法
/// 1.微软提供的浅拷贝
/// 2.序列化
/// 3.反射拷贝
///结论:int,bool等值类型和string浅拷贝之后修改原始值不会影响clone值,但引用类型会影响
/// </summary>
public class CloneDemo : MonoBehaviour
{
private void Start()
{
#region 例子1 //测试修改一个只有基础数据结构的类,结论:int和string浅拷贝之后修改源始值不会影响clone值
XCharacter role = new XCharacter() {Id = 1001, Name = "亚瑟", Hp = 3449, Prof = "战士", Skin = new XSkin() {Name = "死亡骑士"}};
Debug.Log($"原始值:{role.ToString()}");
XCharacter simpleClone = role.Clone() as XCharacter;
XCharacter deepClone = role.DeepClone();
role.Id = 1005;
role.Name = "兰陵王";
role.Prof = "刺客";
role.Skin.Name = "影刃";
Debug.Log($"修改原始值,原始值:{role.ToString()},浅拷贝:{simpleClone.ToString()},深拷贝:{deepClone.ToString()}");
//输出:修改原始值,
//原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃,
//浅拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:影刃,
//深拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士 simpleClone.Id = 1008;
simpleClone.Prof = "刺客";
simpleClone.Name = "李白";
Debug.Log($"修改浅拷贝的值,原始值:{role.ToString()},浅拷贝:{simpleClone.ToString()},深拷贝:{deepClone.ToString()}");
//输出:修改浅拷贝的值,
//原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃,
//浅拷贝:Id:1008,Name:李白,Hp:3449,Prof:刺客,Skin:影刃,
//深拷贝:Id:1001,Name:亚瑟,Hp:3449,Prof:战士,Skin:死亡骑士 #endregion #region 通过反射拷贝 XCharacter reflectClone = new XCharacter();
ReflectClone(role, reflectClone);
Debug.Log($"反射拷贝,原始值:{role.ToString()},反射拷贝:{reflectClone.ToString()}");
//输出:反射拷贝,
//原始值:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃,
//反射拷贝:Id:1005,Name:兰陵王,Hp:3449,Prof:刺客,Skin:影刃
#endregion
} } [Serializable]
class XCharacter : ICloneable
{
public int Id { get; set; }
public string Name { get; set; }
public int Hp;
public string Prof;
public XSkin Skin { get; set; } public override string ToString()
{
return $"Id:{Id},Name:{Name},Hp:{Hp},Prof:{Prof},Skin:{Skin?.ToString()}";
} public object Clone()
{
return this.MemberwiseClone();
} public XCharacter DeepClone()
{
using (Stream objectStream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectStream, this);
objectStream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(objectStream) as XCharacter;
}
}
} [Serializable]
class XSkin
{
public string Name { get; set; } public override string ToString()
{
return this.Name;
}
}

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. 使用 cron4j-solon-plugin 开发定时任务(新)

    (一)新建一个 maven 空项目 (二)添加 maven 引用 <dependency> <groupId>org.noear</groupId> <art ...

  2. html jquery from 表单提交 application/x-www-form-urlencoded 改成 json

    html jquery from 表单提交 $(form).ajaxSubmit  application/x-www-form-urlencoded  改成 json 要使用 jQuery 根据 n ...

  3. 压测工具 wrk

    wrk 是一款针对 Http 协议的基准测试工具,它能够在单机多核 CPU 的条件下,使用系统自带的高性能 I/O 机制,如 epoll,kqueue 等,通过多线程和事件模式,对目标机器产生大量的负 ...

  4. 【每日一题】36. 小AA的数列 (二进制DP)

    补题链接:Here 算法涉及:位运算,DP 这道题想了很久但实在没想什么巧妙的解法,暴力的代码就不放,这里引用Kur1su 的思路 异或问题优先考虑二进制位,对于这个问题,我们需要考虑偶数长度的区间, ...

  5. 编译busybox错误汇总

     提示错误: arm-linux-gcc:Command not found PATH里有/usr/oca/arm/bin,但是make的时候,就是找不到 arm-linux-gcc   原因: ex ...

  6. Java 内存管理最佳实践

    本文翻译自国外论坛 medium,原文地址:https://medium.com/@fullstacktips/best-practices-for-memory-management-in-java ...

  7. C#实现斐波拉切数列求和

    C#实现斐波拉切数列求和 private void button1_Click(object sender, EventArgs e) { listBox1.Items.Clear();//清空Lis ...

  8. 通过 Feign 进行文件上传

    转载请注明出处: 项目为spring cloud 项目,项目中对各部分能力业务进行了拆分,将公共的服务能力放在一个模块当中,通过 Feign 的方式 进行调用,feign 调用的本质还是http内部通 ...

  9. spring-transaction源码分析(4)AspectJ和spring-aspects模块

    AspectJ是Java语言实现的一个面向切面编程的扩展库,能够基于一定的语法编写Aspect代码,使用ajc编译器将其编译成.class文件,之后在Java程序编写或加载时将Aspect逻辑嵌入到指 ...

  10. P2550 [AHOI2001] 彩票摇奖

    1.题目介绍 [AHOI2001] 彩票摇奖 题目描述 为了丰富人民群众的生活.支持某些社会公益事业,北塔市设置了一项彩票.该彩票的规则是: 每张彩票上印有 \(7\) 个各不相同的号码,且这些号码的 ...