阿杰,我来把 .NET 深拷贝(Deep Copy) 给你系统讲透,不仅告诉你原理,还会给你常用的几种实现方式和注意事项。
1. 深拷贝 vs 浅拷贝
类型 | 特点 | 引用类型字段处理 | 场景 |
---|---|---|---|
浅拷贝(Shallow Copy) | 只复制对象本身及其值类型字段 | 复制引用的地址(两个对象共享同一引用数据) | 临时复制,原对象改引用内容会影响新对象 |
深拷贝(Deep Copy) | 递归复制整个对象,包括它引用的子对象 | 每个引用字段都会创建新的副本 | 数据隔离、并发安全 |
例子:
class Person { public string Name; }
var p1 = new Person { Name = "阿杰" };
var p2 = p1; // 浅拷贝:只是复制引用
p2.Name = "小李"; // p1.Name 也变了
// 深拷贝:p2 是新对象,修改不会影响 p1
2. 浅拷贝的内置方式(只是对比)
Person p2 = (Person)p1.MemberwiseClone();
MemberwiseClone()
是受保护的(protected
),只能在类内部调用。它不递归拷贝引用类型,所以只是浅拷贝。
3. 深拷贝的常见实现方法
方法 1:手动递归复制
适合字段少、结构简单的对象。
public class Person
{
public string Name;
public Address Addr;
public Person DeepCopy()
{
return new Person
{
Name = this.Name, // string 是不可变类型,直接赋值即可
Addr = new Address { City = this.Addr.City }
};
}
}
public class Address
{
public string City;
}
✅ 优点:性能高、可控
❌ 缺点:字段多、层级深时很繁琐
方法 2:利用序列化(BinaryFormatter / DataContract / JSON)
适合快速实现,自动处理嵌套引用。
2.1 二进制序列化(旧方法,不推荐在新项目中)
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
⚠️ 需要 [Serializable]
特性,.NET 5+ 已弃用 BinaryFormatter
。
2.2 JSON 序列化(推荐)
用 System.Text.Json
或 Newtonsoft.Json
:
using System.Text.Json;
public static T DeepClone<T>(T obj)
{
var json = JsonSerializer.Serialize(obj);
return JsonSerializer.Deserialize<T>(json);
}
✅ 不需要 [Serializable]
❌ 性能比手动拷贝差,不支持循环引用(除非特殊配置)
方法 3:反射拷贝
动态遍历所有字段并递归拷贝。
using System;
using System.Reflection;
public static class DeepCopyHelper
{
public static T DeepCopy<T>(T obj)
{
if (obj == null) return default;
var type = obj.GetType();
// 值类型或字符串直接返回
if (type.IsValueType || type == typeof(string))
return obj;
// 创建新实例
var result = Activator.CreateInstance(type);
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var fieldValue = field.GetValue(obj);
field.SetValue(result, DeepCopy(fieldValue));
}
return (T)result;
}
}
✅ 自动化,不用写每个字段
❌ 性能较低,不支持某些特殊类型(如未实现无参构造)
方法 4:AutoMapper(第三方库)
快速把一个对象映射成另一个对象。
using AutoMapper;
var config = new MapperConfiguration(cfg => cfg.CreateMap<Person, Person>());
var mapper = config.CreateMapper();
Person p2 = mapper.Map<Person>(p1);
✅ 简洁,尤其适合 DTO ↔ Entity 转换
❌ 配置麻烦,依赖外部库
4. 循环引用问题
深拷贝时如果对象 A 引用对象 B,而 B 又引用回 A,会导致递归死循环。
解决方法:
- 用字典记录已拷贝的对象,重复引用直接返回现有副本
- 序列化时开启循环引用处理(如 Newtonsoft.Json 的
PreserveReferencesHandling
)
5. 性能对比(大致趋势)
方法 | 性能 | 可维护性 | 限制 |
---|---|---|---|
手动写拷贝 | ⭐⭐⭐⭐⭐ | 差 | 要改代码 |
反射递归 | ⭐⭐ | 好 | 慢 |
JSON 序列化 | ⭐⭐ | 好 | 循环引用需处理 |
AutoMapper | ⭐⭐⭐ | 好 | 需配置 |
发表回复