C#中对象克隆方法集锦,详细实例讲解

概述:在许多软件开发方案中,在 C# 中创建对象的深层副本是常见的要求。如果需要具有相同数据但独立于原始对象的对象的精确副本,则必须进行克隆。这意味着对克隆对象的更改不应影响原始对象。这在涉及复杂对象图的方案中特别有用,或者在处理保持需要保留或复制的状态而不影响原始状态的对象时特别有用。1. 手动克隆 🛠️问题陈述想象一下财务应用程序中的一个场景,其中您有一个包含各种财务详细信息和相关对象的对象。您需要克隆此对象以模拟不同的财务场景,而无需更改原始客户的数据。CustomerCustomer手动克隆解决方案public class Customer {     public string Na

在许多软件开发方案中,在 C# 中创建对象的深层副本是常见的要求。如果需要具有相同数据但独立于原始对象的对象的精确副本,则必须进行克隆。这意味着对克隆对象的更改不应影响原始对象。这在涉及复杂对象图的方案中特别有用,或者在处理保持需要保留或复制的状态而不影响原始状态的对象时特别有用。

1. 手动克隆 🛠️

问题陈述

想象一下财务应用程序中的一个场景,其中您有一个包含各种财务详细信息和相关对象的对象。您需要克隆此对象以模拟不同的财务场景,而无需更改原始客户的数据。CustomerCustomer

手动克隆解决方案

public class Customer  
{  
    public string Name { get; set; }  
    public decimal Balance { get; set; }  
    // Assume more complex properties and objects here  
  
    // Manual cloning  
    public Customer Clone()  
    {  
        return new Customer { Name = this.Name, Balance = this.Balance };  
        // Clone more complex properties and objects here  
    }  
}

优点:完全控制克隆过程。

缺点:乏味且容易出错,尤其是对于复杂的对象图。

2. ICloneable 接口 📑

问题陈述

考虑一个带有类的库存系统。复制相似但不同的项目的产品时,需要克隆。Product

ICloneable 解决方案

public class Product : ICloneable  
{  
    public string Name { get; set; }  
    public decimal Price { get; set; }  
  
    public object Clone()  
    {  
        return this.MemberwiseClone(); // Shallow copy  
    }  
}

优点:易于实施。

缺点:仅创建浅拷贝。深度克隆需要手动实现。

3. 序列化和反序列化 🔄

问题陈述

在内容管理系统中,您有一个复杂的对象,其中包括元数据、内容、作者详细信息等。克隆此对象需要可以处理嵌套对象的深层复制机制。Article

序列化解决方案

public static T Clone<T>(T source)  
{  
    if (!typeof(T).IsSerializable)  
    {  
        throw new ArgumentException("The type must be serializable.", nameof(source));  
    }  
  
    if (Object.ReferenceEquals(source, null))  
    {  
        return default(T);  
    }  
  
    IFormatter formatter = new BinaryFormatter();  
    Stream stream = new MemoryStream();  
    using (stream)  
    {  
        formatter.Serialize(stream, source);  
        stream.Seek(0, SeekOrigin.Begin);  
        return (T)formatter.Deserialize(stream);  
    }  
}

优点:易于深度克隆。

缺点:类型必须是可序列化的。性能开销。

4. 反射 🔍

问题陈述

您正在开发一个需要克隆对象的框架,但在编译时不知道对象类型。

反射解决方案

public static T CloneWithReflection\<T>(T source)  
{  
    Type type = source.GetType();  
    if (type.IsPrimitive || typeof(string) == type)  
    {  
        return source;  
    }  
  
    if (type.IsArray)  
    {  
        Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));  
        var array = source as Array;  
        Array cloned = Array.CreateInstance(elementType, array.Length);  
        for (int i = 0; i < array.Length; i++)  
        {  
            cloned.SetValue(CloneWithReflection(array.GetValue(i)), i);  
        }  
        return (T)Convert.ChangeType(cloned, type);  
    }  
  
    object clone = Activator.CreateInstance(type);  
    while (type != null && type != typeof(object))  
    {  
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))  
        {  
            object fieldValue = field.GetValue(source);  
            if (fieldValue == null)  
            {  
                continue;  
            }  
            field.SetValue(clone, CloneWithReflection(fieldValue));  
        }  
        type = type.BaseType;  
    }  
    return (T)clone;  
}

5. 用于深度克隆🌳的表达树

问题陈述

对于实时交易系统等高性能场景,克隆需要尽可能高效。

表达树解决方案

表达式树允许您在运行时动态生成和编译代码。您可以利用表达式树生成针对特定类型定制的高效克隆方法。

public static class CloneExpression<T>  
{  
    private static readonly Func<T, T> CloneFunc = CreateCloneFunction();  
  
    public static T Clone(T obj)  
    {  
        return CloneFunc(obj);  
    }  
  
    private static Func<T, T> CreateCloneFunction()  
    {  
        var type = typeof(T);  
        var original = Expression.Parameter(type, "original");  
        var clone = Expression.Variable(type, "clone");  
  
        var expressions = new List<Expression>();  
        expressions.Add(Expression.Assign(clone, Expression.New(type)));  
  
        foreach (var prop in type.GetProperties())  
        {  
            var originalProp = Expression.Property(original, prop);  
            var cloneProp = Expression.Property(clone, prop);  
            expressions.Add(Expression.Assign(cloneProp, originalProp));  
        }  
  
        expressions.Add(clone);  
  
        var lambda = Expression.Lambda<Func<T, T>>(Expression.Block(new[] { clone }, expressions), original);  
        return lambda. Compile();  
    }  
}

优点:克隆速度极快。

缺点:实现复杂。由于表达式树的编译而产生的初始设置成本。

6. 复杂对象图🗺️的自动映射器

问题陈述

在处理复杂域模型的企业应用程序(例如管理组织层次结构的系统)中,由于这些对象的复杂性以及它们之间的关系,克隆这些对象可能会变得很麻烦。

自动映射器解决方案

Automapper 是一种流行的对象到对象映射库,它可以通过自动为您处理深度复制来简化克隆复杂对象图的任务。虽然 Automapper 主要以其在不同对象类型之间映射的能力而闻名,但它也可以配置为执行深度克隆。

// Configure Automapper  
var config = new MapperConfiguration(cfg => {  
    cfg.CreateMap<Organization, Organization>().ForMember(x => x.ParentOrganization, opt => opt.Ignore());  
    // Configure other mappings  
});  
  
var mapper = config.CreateMapper();  
  
Organization original = new Organization { /* Initialize with data */ };  
Organization clone = mapper.Map<Organization, Organization>(original);

优点:简化了复杂对象图的克隆。减少样板代码。

缺点:外部依赖。需要初始设置和配置。

7.使用 System.Text.Json 进行深度克隆 🔄

问题陈述

对于频繁序列化和反序列化数据的应用程序(如 Web API),利用序列化进行克隆可以有效地利用现有机制。

System.Text.Json 解决方案

随着 .NET Core 3.0 及更高版本的引入,有一种高效且高性能的方法来执行深度克隆,方法是将对象序列化为 JSON,然后将其反序列化回来。System.Text.Json

public static T Clone<T>(T source)  
{  
    var json = JsonSerializer.Serialize(source);  
    return JsonSerializer.Deserialize<T>(json);  
}

优点:利用提供了一种高性能的框架原生方法来克隆对象。System.Text.Json

缺点:要求对象可使用 .与更直接的克隆方法相比,对于非常复杂或深度嵌套的对象图可能没有那么有效。System.Text.Json

8. 不可变对象克隆📜的记录

问题陈述

在以不可变性为关键的应用程序中,例如函数式编程范例或线程安全问题涉及的并发系统,克隆对象可以通过使用记录来获得固有的支持。

使用 C# 记录的解决方案

记录是在 C# 9 中引入的,是一种定义不可变引用类型的方法。它们提供了一种内置机制,用于创建实例的副本,并可以选择更改某些属性,称为非破坏性突变。

public record Person(string Name, int Age);  
  
var original = new Person("John Doe", 30);  
var clone = original with { Age = 31 }; // Clone with modification

优点:内置支持不可变性克隆。简化不可变对象模式的代码。

缺点:仅限于 C# 9 及更高版本。仅适用于需要不可变性的方案。

9. 用于克隆🧙 ♂️的面向方面的编程 (AOP)

问题陈述

在具有跨领域问题(如日志记录、缓存和克隆)的大型应用程序中,维护代码可能会变得繁琐并导致代码重复。面向方面的编程 (AOP) 允许将这些关注点与主要业务逻辑分开。

AOP解决方案

PostSharp 或 Fody 等工具可以在编译时或运行时将克隆逻辑注入到对象中,具体取决于配置。这可以通过以声明方式应用它来简化整个应用程序的克隆。

// Using PostSharp for cloning  
[Serializable]  
public class CloneableAspect : InstanceLevelAspect, ICloneable  
{  
    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]  
    public object Clone()  
    {  
        return this.GetType().GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(this, null);  
    }  
}  
  
[CloneableAspect]  
public class MyBusinessObject  
{  
    public string Property1 { get; set; }  
    // Other properties  
}

优点:减少样板克隆代码。将克隆逻辑与业务逻辑分开,以获得更简洁的代码。

缺点:需要额外的依赖关系和对 AOP 的理解。可能会引入使代码更难理解和调试的魔力。

10. 克隆✨的扩展方法

问题陈述

在具有需要克隆的多种对象类型但缺少用于克隆的通用接口或基类的系统中,直接在每个类中实现克隆可能会导致重大的代码复制和维护挑战。

扩展方法解决方案

扩展方法提供了一种向现有类型添加新方法而不修改它们的方法。我们可以创建一种通用的扩展方法,利用反射进行深度克隆,提供适用于任何对象的灵活解决方案。

public static class CloningExtensions  
{  
    public static T DeepClone<T>(this T source)  
    {  
        if (source == null) return default;  
        var type = source.GetType();  
        if (type.IsValueType || type == typeof(string)) return source;  
  
        var clone = Activator.CreateInstance(type);  
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))  
        {  
            var fieldValue = field.GetValue(source);  
            if (fieldValue == null) continue;  
            field.SetValue(clone, fieldValue.DeepClone());  
        }  
        return (T)clone;  
    }  
}

优点:易于应用于任何对象。减少每个类中对克隆逻辑的需求。 缺点: 基于反射,因此比编译时解决方案慢。可能无法正确处理所有深度克隆情况。

11. 用于克隆🎭的动态代理生成

问题陈述

对于需要运行时灵活性的应用程序,例如实现复杂动态行为或运行时对象修改的应用程序(例如,模拟框架、插件),传统的静态克隆方法并不适用。

动态代理解决方案

像 Castle DynamicProxy 这样的库可以在运行时拦截对象创建和方法调用,从而实现动态克隆功能。这涉及创建一个可以拦截克隆过程的代理,动态创建对象的克隆。

// This is a conceptual example; actual implementation may vary significantly  
public class CloneInterceptor : IInterceptor  
{  
    public void Intercept(IInvocation invocation)  
    {  
        if (invocation.Method.Name == "MemberwiseClone")  
        {  
            // Implement custom cloning logic here  
            // This could involve creating a new instance of the object and manually copying fields and properties  
        }  
        else  
        {  
            invocation.Proceed();  
        }  
    }  
}  
  
public static T CloneViaProxy<T>(T source) where T : class  
{  
    var proxyGenerator = new ProxyGenerator();  
    return proxyGenerator.CreateClassProxyWithTarget(source, new CloneInterceptor());  
}

优点:高度灵活和强大,允许运行时克隆行为调整。缺点:引入复杂性和开销。需要对动态代理及其性能影响有深入的了解。

12. Blittable 类型的💾内存复制

问题陈述

在低级系统或性能关键型代码路径(如图形呈现或密集型数值计算)中,反射或基于序列化的克隆的开销可能令人望而却步。

内存复制解决方案

对于可流送类型(在非托管内存中具有直接等效项的类型),可以使用直接内存复制来克隆对象。此方法使用低级内存操作将数据从一个对象复制到另一个对象。

public static T CloneByMemory<T>(T source) where T : struct  
{  
    T clone = default;  
    var size = Marshal.SizeOf(typeof(T));  
    var sourcePtr = Marshal.AllocHGlobal(size);  
    var destPtr = Marshal.AllocHGlobal(size);  
      
    try  
    {  
        Marshal.StructureToPtr(source, sourcePtr, false);  
        CopyMemory(destPtr, sourcePtr, size);  
        clone = Marshal.PtrToStructure<T>(destPtr);  
    }  
    finally  
    {  
        Marshal.FreeHGlobal(sourcePtr);  
        Marshal.FreeHGlobal(destPtr);  
    }  
  
    return clone;  
}  
  
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]  
public static extern void CopyMemory(IntPtr dest, IntPtr src, int size);

优点:对于可流式类型来说,速度非常快。无需进行反射或序列化。

缺点:仅限于可流通类型。需要不安全的代码和仔细的内存管理,以避免泄漏或损坏。

13.使用 MemberwiseClone() 进行浅层克隆

该方法提供了一种快速创建对象浅拷贝的方法。但是,请务必注意,此方法仅将对象的值类型和引用复制到引用类型的相同内存位置。MemberwiseClone()

public class Person  
{  
    public string Name { get; set; }  
    public Address Address { get; set; }  
  
    public Person ShallowClone()  
    {  
        return (Person)this.MemberwiseClone();  
    }  
}  
  
public class Address  
{  
    public string Street { get; set; }  
    public string City { get; set; }  
}

用例:假设您有一个具有属性和引用类型的类。当您使用 执行浅层克隆时,克隆的对象将具有自己的值,但它将与原始对象共享相同的引用。修改克隆对象也会影响原始对象。PersonNameAddressMemberwiseClone()PersonNameAddressAddress

14.IL克隆

IL(中间语言)发出是一种低级技术,允许您通过直接发出 IL 指令来生成动态方法。您可以使用 IL Emit 创建高度优化的克隆方法。

public static class CloneEmit<T>  
{  
    private static readonly Func<T, T> CloneFunc = CreateCloneFunction();  
  
    public static T Clone(T obj)  
    {  
        return CloneFunc(obj);  
    }  
  
    private static Func<T, T> CreateCloneFunction()  
    {  
        var type = typeof(T);  
        var method = new DynamicMethod("CloneMethod", type, new[] { type }, true);  
        var il = method.GetILGenerator();  
  
        il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));  
        il.Emit(OpCodes.Stloc_0);  
  
        foreach (var prop in type.GetProperties())  
        {  
            il.Emit(OpCodes.Ldloc_0);  
            il.Emit(OpCodes.Ldarg_0);  
            il.Emit(OpCodes.Callvirt, prop.GetGetMethod());  
            il.Emit(OpCodes.Callvirt, prop.GetSetMethod());  
        }  
  
        il.Emit(OpCodes.Ldloc_0);  
        il.Emit(OpCodes.Ret);  
  
        return (Func<T, T>)method.CreateDelegate(typeof(Func<T, T>));  
    }  
}

使用案例:IL Emit 适用于需要最高性能水平进行对象克隆的场景。通过直接生成 IL 指令,您可以创建性能优于其他技术的高度优化的克隆方法。该类使用 IL Emit 生成动态方法,该方法创建该类型的新实例并复制其所有属性。然后编译并调用生成的方法以有效地克隆对象。CloneEmit<T>

15.使用T4模板克隆

T4(文本模板转换工具包)是 Visual Studio 中的代码生成框架,可用于基于模板生成代码。您可以在设计时使用 T4 模板为特定类型生成克隆代码。

<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>

<#
    var types = new[] { typeof(Person), typeof(Address) };
    foreach (var type in types)
    {
#>
    public static class <#= type.Name #>Cloner
    {
        public static <#= type.Name #> Clone(<#= type.Name #> obj)
        {
            var clone = new <#= type.Name #>();
<#
            foreach (var prop in type.GetProperties())
            {
#>
            clone.<#= prop.Name #> = obj.<#= prop.Name #>;
<#
            }
#>
            return clone;
        }
    }
<#
    }
#>

用例:当您有一组需要克隆的固定类型,并且希望在设计时生成克隆代码时,T4 模板非常有用。通过基于模板生成代码,可以确保类型安全,并避免在运行时产生反射或动态代码生成的开销。上面的 T4 模板为指定类型生成克隆类,每个类都包含一个创建新实例并复制所有属性的方法。Clone

🚀 对象克隆✈️中的高级主题和注意事项

📌 克隆和继承

在处理继承层次结构时,克隆可能会变得更加复杂。必须考虑克隆在整个继承链中的行为方式。

考虑:

  • 确定在继承层次结构的每个级别上克隆应该是浅克隆还是深克隆。
  • 确定克隆是否应保留原始对象的确切类型或创建基类的实例。
  • 考虑虚拟方法和抽象方法对克隆行为的影响。

例:

public abstract class Animal  
{  
    public string Name { get; set; }  
    public abstract Animal DeepClone();  
}  
  
public class Cat : Animal  
{  
    public string Breed { get; set; }  
  
    public override Animal DeepClone()  
    {  
        return new Cat  
        {  
            Name = Name,  
            Breed = Breed  
        };  
    }  
}  
  
public class Dog : Animal  
{  
    public string Breed { get; set; }  
  
    public override Animal DeepClone()  
    {  
        return new Dog  
        {  
            Name = Name,  
            Breed = Breed  
        };  
    }  
}

在此示例中,该类充当抽象基类,定义公共属性和方法。和 类继承并提供了自己的实现,创建了各自类型的实例。AnimalDeepCloneCatDogAnimalDeepClone

📌 克隆和循环引用

如果处理不当,使用循环引用克隆对象可能会导致无限递归。在克隆过程中检测和处理循环引用至关重要。

考虑:

  • 识别对象图中的循环引用,并确定如何在克隆过程中打破循环。
  • 使用引用跟踪或唯一对象标识符等技术来检测和处理循环引用。
  • 请考虑使用基于序列化的克隆方法,以自动处理循环引用。

例:

public class Node  
{  
    public int Value { get; set; }  
    public Node Next { get; set; }  
  
    public Node DeepClone()  
    {  
        var clonedNode = new Node { Value = Value };  
        if (Next != null)  
        {  
            clonedNode.Next = Next.DeepClone();  
            clonedNode.Next.Next = clonedNode;  
        }  
        return clonedNode;  
    }  
}

在此示例中,该类表示具有循环引用的单向链表。该方法创建节点的新实例并以递归方式克隆该节点。为了处理循环引用,它会将克隆节点的引用指派回克隆节点本身。NodeDeepCloneNodeNextNextNext

📌 克隆和不可变性

使用不可变对象时,可以简化克隆。不可变对象一旦创建,就无法修改,从而降低克隆的复杂性。

考虑:

  • 请考虑尽可能将对象设计为不可变对象,以简化克隆。
  • 对于不可变对象,浅层克隆通常就足够了,因为无法修改内部状态。
  • 不可变对象可以在多个引用之间安全地共享,而无需深度克隆。

例:

public class ImmutablePoint  
{  
    public int X { get; }  
    public int Y { get; }  
  
    public ImmutablePoint(int x, int y)  
    {  
        X = x;  
        Y = y;  
    }  
  
    public ImmutablePoint Clone()  
    {  
        return this;  
    }  
}

在此示例中,该类表示具有 和 坐标的不可变点。由于对象是不可变的,因此该方法仅返回当前实例,因为它可以安全地共享,而无需任何修改。ImmutablePointXYClone

📌 克隆和安全性

在实施克隆时,必须考虑安全隐患,尤其是在处理敏感数据时。

考虑:

  • 确保克隆不会暴露或泄露敏感信息。
  • 克隆包含对外部资源或系统级对象的引用的对象时要小心。
  • 请考虑实施访问控制和权限,以根据用户角色或安全级别限制克隆操作。

例:

public class SecureData : ICloneable  
{  
    public string PublicInfo { get; set; }  
    private string PrivateInfo { get; set; }  
  
    public object Clone()  
    {  
        return new SecureData  
        {  
            PublicInfo = PublicInfo  
        };  
    }  
}

在此示例中,该类包含公共信息和私有信息。该方法实现接口,但仅克隆公共信息,确保克隆过程中不会暴露私有敏感数据。SecureDataCloneICloneable

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/79430.html

Like (0)
guozi的头像guozi
Previous 2024年5月30日
Next 2024年5月30日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注