.NET9 实现对象深拷贝和浅拷贝的性能测试

.NET 中,深拷贝(Deep Copy)浅拷贝(Shallow Copy) 是对象复制的两种方式,它们的区别在于是否复制对象内部引用的其他对象。


浅拷贝(Shallow Copy)

定义:

浅拷贝创建一个新对象,并将原对象的所有字段复制到新对象中。如果是值类型字段,直接复制其值;如果是引用类型字段,则只复制引用地址(指针),而不复制引用的对象本身。

特点:
  • 原始对象与副本共享引用类型的成员。
  • 修改其中一个对象的引用成员会影响另一个对象。
  • 通常速度快、开销小。
实现方式:
  • 使用 MemberwiseClone() 方法实现浅拷贝。
示例代码:
csharp 复制代码
public object ShallowCopy()
{
    return this.MemberwiseClone();
}
图示说明:
bash 复制代码
OriginalPerson.Address ───┐
                          ├─ 指向同一个 Address 对象
CopyPerson.Address   ─────┘

深拷贝(Deep Copy)

定义:

深拷贝不仅复制对象本身,还会递归地复制对象所引用的其他对象,生成一个完全独立的副本。

特点:
  • 原始对象和副本之间没有共享的引用对象。
  • 修改副本不会影响原始对象。
  • 通常比浅拷贝更耗性能。
实现方式:
  1. 手动实现:为每个引用类型字段创建新实例。
  2. 序列化反序列化 (如 System.Text.Json, Newtonsoft.Json 等)。
  3. 表达式树或反射生成器 (如:第三方包 DeepCloner,FastDeepCloner 等)。
示例代码
  • 使用 JSON 序列化:
csharp 复制代码
public Person DeepCopy()
{
    var json = JsonSerializer.Serialize(this);
    return JsonSerializer.Deserialize<Person>(json)!;
}
图示说明:
bash 复制代码
OriginalPerson.Address ───┐
                          ├─ 指向不同的 Address 对象(内容相同)
CopyPerson.Address   ─────┘

总结对比

特性 浅拷贝 深拷贝
引用对象复制 不复制,仅复制引用 复制,生成新对象
内存占用
性能 较慢
修改互不影响 否(修改引用对象会互相影响) 是(完全独立)
典型实现方法 MemberwiseClone() 序列化、手动复制、第三方库

对象设计

设计一个 Person 类,里面包含了一个 Address 引用类型字段:

csharp 复制代码
internal class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public Address Address { get; set; } = new();
}
  • ShallowCopy():复制了 NameAge 的值,但 Address 字段只是复制了引用。
  • DeepCopy() :完整复制整个对象图,包括 Address 实例。 .NET 9 推荐使用 System.Text.Json 或其他现代方式实现深拷贝。

📦 项目准备

  • .net 控制台项目信息
csharp 复制代码
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net9.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<PublishAot>true</PublishAot>
		<InvariantGlobalization>true</InvariantGlobalization>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Datadog.Trace.BenchmarkDotNet" Version="2.61.0" />
		<PackageReference Include="System.Text.Json" Version="9.0.6" />
	</ItemGroup>

</Project>
  • 创建一个 Person 对象,分别实现对象的深拷贝和浅拷贝方
csharp 复制代码
using System.Text.Json;

namespace BenchmarkTest.examples.Copy;

// 创建一个包含深拷贝和浅拷贝方法的类
public class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public Address Address { get; set; } = new();

    // 浅拷贝
    public object ShallowCopy() => this.MemberwiseClone();

    // 深拷贝 (使用 System.Linq.Expressions 手动实现)
    public Person DeepCopy1() => new()
    {
        Name = this.Name,
        Age = this.Age,
        Address = new Address
        {
            Street = this.Address.Street,
            City = this.Address.City
        }
    };

    // 深拷贝 (使用 System.Text.Json)
    public Person DeepCopy2()
    {
        var json = JsonSerializer.Serialize(this);
        return JsonSerializer.Deserialize<Person>(json)!;
    }
}

[Serializable]
public class Address
{
    public string Street { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
}
  • 拷贝测试类 CopyBenchmark
csharp 复制代码
//===========================
// 拷贝测试类 CopyBenchmark
//===========================

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using Datadog.Trace.BenchmarkDotNet;

namespace BenchmarkTest.examples.Copy;

[DatadogDiagnoser]
[MemoryDiagnoser]
public class CopyBenchmark
{
    private Person _person = new();

    [GlobalSetup]
    public void Setup()
    {
        _person = new Person
        {
            Name = "John",
            Age = 30,
            Address = new Address { Street = "Main St", City = "New York" }
        };
    }

    [Benchmark]
    public object ShallowCopyTest() => _person.ShallowCopy();

    [Benchmark]
    public object DeepCopy1Test() => _person.DeepCopy1();

    [Benchmark]
    public object DeepCopy2Test() => _person.DeepCopy2();

    public static void Run(IConfig config)
    {
        var summary = BenchmarkRunner.Run<CopyBenchmark>(config);
        Console.WriteLine(summary);
    }
}

🏁 启动基准测试

  • Program.cs 中运行基准测试
csharp 复制代码
using BenchmarkDotNet.Configs;
using BenchmarkTest.examples.Copy;
using Datadog.Trace.BenchmarkDotNet;

Console.WriteLine("Hello, BenchmarkDotNetTest!");

var config = DefaultConfig.Instance.WithDatadog();
CopyBenchmark.Run(config);
  • 运行测试
csharp 复制代码
dotnet run -c Release

输出信息:

以下是对 BenchmarkDotNet 测试报告的详细分析与解释:


📊 测试环境概览

项目 说明
BenchmarkDotNet 版本 v0.13.2
操作系统 Windows 11 (10.0.26100)
.NET SDK 9.0.301
运行时 .NET 9.0.6(AOT/AVX2)

🧪 性能指标说明

指标 含义
Mean 平均执行时间(纳秒 ns)
Error 置信区间误差范围(通常为 ± 值)
StdDev 标准差,衡量运行时间波动性
Median 中位数,比平均值更稳定
Gen0 Gen0 GC 次数(每 1000 次操作)
Allocated 每次操作分配的内存大小

🔍 测试方法分析

✅ ShallowCopyTest
  • Mean: 69.06 ns
  • Allocated: 40 B
  • 特点
    • 使用 MemberwiseClone() 实现浅拷贝。
    • 只复制对象本身字段,引用类型字段共享地址。
    • 性能高、内存占用低。
    • 不涉及复杂序列化或深拷贝逻辑。

结论:速度快,适用于不需要独立副本的场景。


✅ DeepCopy1Test
  • Mean: 27.87 ns
  • Allocated: 104 B
  • 实现方式
    • 手动实现的深拷贝(new 新对象并赋值)。
    • 或者使用了高性能的表达式树/源生成器等技术。

结论:相比浅拷贝速度最快(手动 new 实现方式),但内存分配多了2倍多,仍是高效且完全独立的对象副本,适合大多数业务场景。


⚠️ DeepCopy2Test
  • Mean: 1,693.59 ns(约 60 倍于 DeepCopy1)
  • Allocated: 1,168 B(约 10 倍于 DeepCopy1)
  • 实现方式
    • 使用 JSON 序列化反序列化(如 System.Text.Json)。
    • 或者第三方库。

结论 :性能明显下降,适合数据结构复杂但需要通用性的场景。若对性能敏感,应考虑优化深拷贝方式(如手动克隆或使用 FastDeepCloner 等库)。


📌 总结对比

方法 执行时间 内存分配 是否独立副本 推荐用途
ShallowCopyTest ⚠️ 较快 ✅ 最少 ❌ 否 快速只读复制
DeepCopy1Test ✅ 最快 ⚠️ 适中 ✅ 是 高性能深拷贝
DeepCopy2Test ❌ 慢 ❌ 较多 ✅ 是 复杂结构 / 快速开发

🛠️ 建议优化方向

  1. 避免使用 JSON 序列化进行频繁深拷贝,除非对象结构非常复杂。
  2. 优先手动实现深拷贝逻辑 或使用源生成工具(如 FastDeepCloner)提升性能。
  3. 若需通用性,可结合缓存机制减少重复深拷贝开销。
  4. 对性能要求极高时,可使用 Span<T>Memory<T> 进行无堆分配拷贝。
相关推荐
JosieBook10 小时前
【开源】一款开源、跨平台的.NET WPF 通用权限开发框架 (ABP) ,功能全面、界面美观
.net·wpf
界面开发小八哥13 小时前
界面组件DevExpress WPF中文教程:网格视图数据布局 - 数据单元格
.net·wpf·界面控件·devexpress·ui开发
tonydf14 小时前
安利一下Blazor:.NET开发者的全栈“优”选项
.net·全栈
SEO-狼术20 小时前
Red Gate .NET Developer Crack
.net
xiaopengbc20 小时前
[Windows] 微软.Net运行库离线合集包 Microsoft .Net Packages AIO v13.05.25
windows·microsoft·.net
智者知已应修善业1 天前
【c#窗体荔枝计算乘法,两数相乘】2022-10-6
经验分享·笔记·算法·c#·.net
测试者家园1 天前
用 LLM 辅助性能测试报告生成
人工智能·llm·性能测试·ai赋能·智能化测试
时光追逐者1 天前
C#拾遗补漏之 Dictionary 详解
开发语言·c#·.net·.net core
切糕师学AI2 天前
Spire.XLS for .NET 中, 将 Excel 转换为 PDF 时, 如何设置纸张大小为A4纸,并将excel内容分页放置?
pdf·.net·excel·spire