对象属性和字段拷贝的几种方式
微软提供了浅拷贝
- 对于值类型,修改拷贝的值不会影响源对象
- 对于引用类型,修改拷贝后的值会影响源对象,但string特殊,它会拷贝一个副本,互相不会影响
自己实现深拷贝,我了解到的有这几种方法
- 硬核编码,每一个属性和字段都写一遍赋值,这种方法运行速度最快
- 通过反射,最常见的方法,但每次都需要反射
- 通过序列化,需要给类加上[Serializable]标签
- 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;
}
}