C#中的深度克隆:从反射到序列化

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C#中的深拷贝:为什么让每个开发者头疼?

\n\n

你是否曾经在C#中复制过一个对象,然后惊讶地发现对“副本”的修改破坏了原始对象?熟悉吗?这是经典的浅拷贝(shallow copy)问题。当对象包含引用类型字段(其他类、列表、数组)时,MemberwiseClone()仅仅复制了引用。结果,原始对象和副本都指向同一个嵌套对象。

\n\n

在本文中,我们将深入探讨C#中的深度克隆——创建包含所有嵌套对象的完全独立副本的过程。我们将比较5种流行的方法,编写实际代码,进行思维基准测试,并找出哪种方法最适合您的项目。

\n\n

什么是深拷贝?它与浅拷贝有何不同?

\n\n

假设您有一个包含Address的Person类:

\n\n
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

深拷贝解决了这个问题:它递归地复制所有嵌套对象,创建一个完全独立的数据结构。

\n\n

方法1:通过ICloneable和拷贝构造函数手动实现

\n\n

最显而易见但也最费力的方法是实现ICloneable接口或编写自己的DeepClone()方法。

\n\n

实现示例:

\n\n
public 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
    \n
  • 大量样板代码
  • \n
  • 添加新字段时容易忘记更新Clone()方法
  • \n
  • 不适用于第三方类(例如来自库的类)
  • \n
\n\n

何时使用:仅适用于只有2-3个字段的简单DTO,且性能至关重要时(例如在游戏引擎中)。

\n\n

方法2:反射(Reflection)——通用解决方案

\n\n

反射允许编写一个通用的深度克隆方法,适用于任何对象。

\n\n

代码示例:

\n\n
public 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                

博客

书籍推荐