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;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM