C#中的深拷贝:为什么让每个开发者头疼?
\n\n你是否曾经在C#中复制过一个对象,然后惊讶地发现对“副本”的修改破坏了原始对象?熟悉吗?这是经典的浅拷贝(shallow copy)问题。当对象包含引用类型字段(其他类、列表、数组)时,MemberwiseClone()仅仅复制了引用。结果,原始对象和副本都指向同一个嵌套对象。
在本文中,我们将深入探讨C#中的深度克隆——创建包含所有嵌套对象的完全独立副本的过程。我们将比较5种流行的方法,编写实际代码,进行思维基准测试,并找出哪种方法最适合您的项目。
\n\n什么是深拷贝?它与浅拷贝有何不同?
\n\n假设您有一个包含Address的Person类:
public class Address\n{\n public string City { get; set; }\n public string Street { get; set; }\n}\n\npublic class Person\n{\n public string Name { get; set; }\n public int Age { get; set; }\n public Address HomeAddress { get; set; }\n}\n\n使用浅拷贝(普通的MemberwiseClone()),您会得到一个新的Person对象,但其HomeAddress仍然指向与原始对象相同的Address对象。修改了副本中的街道——原始对象中的街道也随之改变。灾难。
深拷贝解决了这个问题:它递归地复制所有嵌套对象,创建一个完全独立的数据结构。
\n\n方法1:通过ICloneable和拷贝构造函数手动实现
\n\n最显而易见但也最费力的方法是实现ICloneable接口或编写自己的DeepClone()方法。
实现示例:
\n\npublic class Address : ICloneable\n{\n public string City { get; set; }\n public string Street { get; set; }\n\n public object Clone()\n {\n return new Address { City = this.City, Street = this.Street };\n }\n}\n\npublic class Person : ICloneable\n{\n public string Name { get; set; }\n public int Age { get; set; }\n public Address HomeAddress { get; set; }\n\n public object Clone()\n {\n return new Person\n {\n Name = this.Name,\n Age = this.Age,\n HomeAddress = (Address)this.HomeAddress?.Clone()\n };\n }\n}\n\n优点:
\n- \n
- 完全控制复制过程 \n
- 最佳性能 \n
- 代码清晰易懂(没有“魔法”) \n
缺点:
\n- \n
- 大量样板代码 \n
- 添加新字段时容易忘记更新Clone()方法 \n
- 不适用于第三方类(例如来自库的类) \n
何时使用:仅适用于只有2-3个字段的简单DTO,且性能至关重要时(例如在游戏引擎中)。
\n\n方法2:反射(Reflection)——通用解决方案
\n\n反射允许编写一个通用的深度克隆方法,适用于任何对象。
\n\n代码示例:
\n\npublic static T DeepCloneByReflection<T>(T source)\n{\n if (source == null) return default;\n\n var type = source.GetType();\n \n // 对于值类型和字符串,直接返回值\n if (type.IsValueType || type == typeof(string))\n return source;\n\n // 对于数组\n if (type.IsArray)\n {\n var arr = source as Array;\n var clonedArr = Array.CreateInstance(type.GetElementType(), arr.Length);\n for (int i = 0; i < arr.Length; i++)\n clonedArr.SetValue(DeepCloneByReflection(arr.GetValue(i)), i);\n