dotnet 值拷贝、浅拷贝、深拷贝详解

简介

.NET 中,值拷贝(Value Copy)主要指的是将一个 值类型 的实例或对象的值复制到另一个变量中,使两个变量之间互不影响。我们可以从几个维度来详细理解:

值拷贝的本质

.NET 中的类型分为:

  • 值类型(Value Type):例如 int、float、bool、DateTime、struct 自定义结构体

  • 引用类型(Reference Type):例如 string(特殊值行为)、class、object、array、List<T>

值类型在赋值时是拷贝值本身,引用类型则是拷贝引用(地址)。

值类型是值拷贝

csharp 复制代码
int a = 10;
int b = a; // 值拷贝
b = 20;

Console.WriteLine(a); // 输出 10,不受 b 更改影响

引用类型是引用拷贝

csharp 复制代码
class Person
{
    public string Name;
}

Person p1 = new Person { Name = "Alice" };
Person p2 = p1; // 引用拷贝,指向同一对象

p2.Name = "Bob";

Console.WriteLine(p1.Name); // 输出 Bob,p1 也被修改了

自定义结构体也是值类型(值拷贝)

csharp 复制代码
struct Point
{
    public int X;
    public int Y;
}

Point p1 = new Point { X = 1, Y = 2 };
Point p2 = p1;  // 值拷贝
p2.X = 100;

Console.WriteLine(p1.X); // 1
Console.WriteLine(p2.X); // 100

结构体之间的赋值是完全拷贝一份内存,不影响原来的结构体变量。

浅拷贝 vs 深拷贝(类场景)

如果需要让 引用类型 的对象像 值类型 一样"复制",就要实现:

  • 浅拷贝(Shallow Copy):复制对象的字段引用

  • 深拷贝(Deep Copy):复制整个对象图结构

使用 MemberwiseClone() 实现浅拷贝(只能在类内部使用)

csharp 复制代码
public class Person
{
    public string Name;
    public Address Address;

    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
}

public class Address
{
    public string City;
}

// 使用
var original = new Person { Name = "Alice", Address = new Address { City = "New York" } };
var copy = original.ShallowCopy();

copy.Name = "Bob";
copy.Address.City = "London";

Console.WriteLine(original.Name);      // 输出: Alice
Console.WriteLine(original.Address.City); // 输出: London(共享引用)

实现深拷贝的常用方式

  • 自己 new 一个新对象,手动复制字段
csharp 复制代码
public class Person
{
    public string Name;
    public Address Address;

    public Person DeepCopy()
    {
        return new Person
        {
            Name = this.Name,
            Address = new Address { City = this.Address.City }
        };
    }
}

// 使用
var original = new Person { Name = "Alice", Address = new Address { City = "New York" } };
var copy = original.DeepCopy();

copy.Name = "Bob";
copy.Address.City = "London";

Console.WriteLine(original.Name);      // 输出: Alice
Console.WriteLine(original.Address.City); // 输出: New York(独立)
  • 使用序列化反序列化(如 Newtonsoft.JsonSystem.Text.Json
csharp 复制代码
public static T DeepCopy<T>(T obj)
{
    string json = JsonSerializer.Serialize(obj);
    return JsonSerializer.Deserialize<T>(json);
}

// 使用
var original = new Person { Name = "Alice", Address = new Address { City = "New York" } };
var copy = DeepCopy(original);

copy.Address.City = "London";
Console.WriteLine(original.Address.City); // 输出: New York
  • 反射或表达式树:动态生成深拷贝逻辑。
csharp 复制代码
public static object DeepCopyReflection(object original) {
    if (original == null) return null;
    Type type = original.GetType();
    object copy = Activator.CreateInstance(type);
    foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
        object value = field.GetValue(original);
        if (value is ICloneable cloneable) 
            field.SetValue(copy, cloneable.Clone());
        else 
            field.SetValue(copy, DeepCopyReflection(value));
    }
    return copy;
}
  • 使用第三方库:DeepCloner 实现深拷贝
csharp 复制代码
using System;
using Force.DeepCloner;

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string City { get; set; }
}

class Program
{
    static void Main()
    {
        var original = new Person
        {
            Name = "Alice",
            Address = new Address { City = "New York" }
        };

        // 深拷贝对象
        var copy = original.DeepClone();

        copy.Name = "Bob";
        copy.Address.City = "London";

        Console.WriteLine(original.Name);       // 输出: Alice
        Console.WriteLine(original.Address.City); // 输出: New York(独立)
    }
}
  • 使用第三方库:DeepCloner 处理复杂对象图

DeepCloner 支持嵌套对象、集合和循环引用的深拷贝。

csharp 复制代码
public class Company
{
    public List<Employee> Employees { get; set; }
}

public class Employee
{
    public string Name { get; set; }
    public Company Company { get; set; }
}

class Program
{
    static void Main()
    {
        var company = new Company();
        var employee = new Employee { Name = "Alice", Company = company };
        company.Employees = new List<Employee> { employee };

        // 深拷贝(处理循环引用)
        var companyCopy = company.DeepClone();

        companyCopy.Employees[0].Name = "Bob";
        Console.WriteLine(company.Employees[0].Name); // 输出: Alice(独立)
    }
}

性能优化

DeepCloner 通过缓存和 IL 代码生成实现高性能深拷贝,适合高频调用场景。

基准对比

方法 耗时(1000次深拷贝) 内存占用
DeepCloner ~10 ms
Json序列化 ~200 ms
手动深拷贝 ~5 ms(简单对象)
  • 使用 AutoMapper 进行深度拷贝
csharp 复制代码
using AutoMapper;
using System;

// 定义源对象类
public class Source
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// 定义目标对象类
public class Destination
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Program
{
    static void Main()
    {
        // 配置映射
        var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>());
        var mapper = config.CreateMapper();

        // 创建源对象
        var source = new Source { Id = 1, Name = "Example" };

        // 进行映射(拷贝)
        var destination = mapper.Map<Destination>(source);

        Console.WriteLine($"Id: {destination.Id}, Name: {destination.Name}");
    }
}
  • 使用 ExpressMapper 进行深度拷贝

ExpressMapper 是一个高性能的对象映射库,它利用表达式树来生成映射代码,从而实现快速的对象映射和拷贝。它支持复杂的映射规则和自定义转换,并且具有良好的性能表现。

csharp 复制代码
using ExpressMapper;
using System;

// 定义源对象类
public class SourceClass
{
    public int Value { get; set; }
}

// 定义目标对象类
public class DestinationClass
{
    public int Value { get; set; }
}

class Program
{
    static void Main()
    {
        // 配置映射
        Mapper.Register<SourceClass, DestinationClass>();

        // 创建源对象
        var source = new SourceClass { Value = 10 };

        // 进行映射(拷贝)
        var destination = Mapper.Map<SourceClass, DestinationClass>(source);

        Console.WriteLine($"Value: {destination.Value}");
    }
}
相关推荐
三天不学习1 小时前
C# + Python混合开发实战:优势互补构建高效应用
开发语言·python·c#
谢道韫6661 小时前
37-串联所有单词的子串
开发语言·算法·c#
CodeCraft Studio3 小时前
PDF处理控件Aspose.PDF指南:使用 C# 从 PDF 文档中删除页面
前端·pdf·c#
全栈小53 小时前
【C#】Html转Pdf,Spire和iTextSharp结合,.net framework 4.8
pdf·c#·html
三天不学习3 小时前
混合开发部署实战:PyInstaller + .NET 8 + Docker全链路配置
python·docker·容器·.net
电商api接口开发12 小时前
如何在C#中使用LINQ对数据库进行查询操作?
数据库·c#·linq
搬砖工程师Cola12 小时前
<C#>在 .NET 开发中,依赖注入, 注册一个接口的多个实现
开发语言·c#·.net
vil du16 小时前
C# string和其他引用类型的区别
c#