C#对象属性浅拷贝和深拷贝

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

微软提供了浅拷贝

  • 对于值类型,修改拷贝的值不会影响源对象
  • 对于引用类型,修改拷贝后的值会影响源对象,但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;
    }
}
posted @ 2021-05-23 11:39  赵青青  阅读(2693)  评论(0编辑  收藏  举报