导读:本期聚焦于小伙伴创作的《C#如何实现原型模式?ICloneable与MemberwiseClone深浅拷贝详解》,敬请观看详情。直接调用MemberwiseClone方法只能得到对象的浅拷贝,引用类型字段仍指向同一块内存,修改副本会污染原对象。原型模式的核心是通过克隆接口隔离对象创建细节,在C#中可让类实现ICloneable并自行控制深拷贝逻辑。本文从内存布局讲清值类型与引用类型在复制时的差异,给出用序列化、递归赋值实现深拷贝的多种写法,并分析每种方案在循环引用、性能上的表现,帮助你在业务里安全地复用对象模板。

原型模式属于创建型设计模式,核心思想是通过复制已有对象来生成新实例,从而避免重复执行昂贵的初始化逻辑。在C#里,所有引用类型都隐式继承自System.Object,而Object提供了一个受保护的MemberwiseClone方法,这成了实现克隆的天然入口。理解它与ICloneable接口的配合方式,是写好原型模式的关键。

C#如何实现原型模式?ICloneable与MemberwiseClone深浅拷贝详解

一、MemberwiseClone的浅拷贝本质

MemberwiseClone是Object的受保护方法,它创建当前对象的一个新实例,然后将当前对象的所有非静态字段逐位复制过去。对于值类型字段,复制的是实际数据;对于引用类型字段,复制的仅仅是引用地址,也就是说原对象和克隆对象会共享同一个引用对象。

下面这段代码展示了浅拷贝带来的副作用:修改克隆体的引用类型字段,原对象也跟着变了。这种特性在需要独立副本的业务里是隐患,比如缓存模板对象被多个请求复用后又被意外改写。

using System;

public class Address
{
    public string City;
}

public class Person
{
    public string Name;
    public int Age;
    public Address Address;

    public Person ShallowCopy()
    {
        // 调用Object的受保护方法做浅拷贝
        return (Person)this.MemberwiseClone();
    }
}

class Program
{
    static void Main()
    {
        Person p1 = new Person { Name = "Tom", Age = 20, Address = new Address { City = "Beijing" } };
        Person p2 = p1.ShallowCopy();

        p2.Address.City = "Shanghai";
        Console.WriteLine(p1.Address.City); // 输出 Shanghai,原对象被污染
    }
}

二、ICloneable接口与规范克隆

ICloneable是.NET提供的标准克隆接口,只定义了一个Clone方法。让业务类实现该接口,可以把克隆逻辑收敛到类内部,调用方不需要关心是深拷贝还是浅拷贝。不过官方文档也提醒,ICloneable并没有规定Clone必须返回深拷贝还是浅拷贝,因此团队内部需要约定清楚。

我们在Person类上实现ICloneable,并在Clone方法里手动创建引用类型的新实例,从而实现深拷贝。这种方式直观可控,也能顺带处理字段重命名的兼容问题。

using System;

public class Address
{
    public string City;
    public Address Clone()
    {
        return new Address { City = this.City };
    }
}

public class Person : ICloneable
{
    public string Name;
    public int Age;
    public Address Address;

    public object Clone()
    {
        Person copy = (Person)this.MemberwiseClone();
        copy.Address = this.Address.Clone(); // 手动复制引用对象
        return copy;
    }
}

class Program
{
    static void Main()
    {
        Person p1 = new Person { Name = "Tom", Age = 20, Address = new Address { City = "Beijing" } };
        Person p2 = (Person)p1.Clone();

        p2.Address.City = "Shanghai";
        Console.WriteLine(p1.Address.City); // 输出 Beijing,原对象安全
    }
}

三、通用深拷贝的几种实现思路

当对象图很复杂时,手写每个引用类型的Clone容易遗漏。此时可以用序列化反序列化方式做通用深拷贝:把对象写成二进制或JSON流再读回来,得到的一定是全新对象树。这种写法对类有要求,比如字段要可序列化、无循环引用。

另一种思路是用反射递归遍历属性赋值,不依赖序列化特性,但性能比手写克隆差。下面给出基于二进制序列化的深拷贝辅助方法,适用于标记了Serializable的类型。

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
public class Address
{
    public string City;
}

[Serializable]
public class Person
{
    public string Name;
    public Address Address;
}

public static class DeepCloneHelper
{
    public static T DeepClone<T>(T obj)
    {
        if (obj == null) return default(T);
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(ms);
        }
    }
}

四、原型模式在业务中的落地建议

在订单、报表等模板化场景,原型模式能明显减少重复构建开销。建议把克隆方法做成受保护或显式接口,避免外部随意浅拷贝。对于包含非托管资源或事件委托的对象,克隆时要额外断开事件绑定,防止副本收到原对象通知。

性能方面,手写Clone优于反射和序列化;若对象极小且创建成本低,直接new可能比克隆更清晰。选择方案时权衡对象复杂度与调用频率,才能让原型模式真正提升代码可维护性。

方案速度适用场景
MemberwiseClone浅拷贝最快无引用类型字段或允许共享
手写ICloneable深拷贝对象图稳定、追求性能
序列化深拷贝通用工具、类型多变

C#ICloneableMemberwiseClone修改时间:2026-08-08 10:57:13

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。