zoukankan      html  css  js  c++  java
  • 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;
        }
    }
    
  • 相关阅读:
    软工总结博客
    第四次个人博客
    第三次博客作业
    结对项目作业
    第二次博客作业
    个人博客作业_week14
    个人博客作业_week7
    结对编程_附加题_博客2
    结对编程1_四则运算器_博客1
    个人博客作业_week3
  • 原文地址:https://www.cnblogs.com/zhaoqingqing/p/14800759.html
Copyright © 2011-2022 走看看