65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Framework 中克隆对象 - 第一部分

2016 年 12 月 27 日

CPOL

4分钟阅读

viewsIcon

65998

.NET Framework 中克隆对象的重要性

引言

在本文中,我们将展示在 .NET Framework 中克隆对象的多种方法。 每种克隆模式都会分析其优缺点。

此说明不适用于不可变类(字符串、委托、结构等),因为这些类具有其他行为,并且不在本文中讨论。

为此,我们将使用两种实现技术,首先是 ICloneable Interface,其次是依赖于克隆类型的扩展方法。

背景

这是一个非常重要的程序部分。 如果您不是基本开发人员,则可以跳过此部分.

在高级语言(C#、Java、C++ 等)中,当我们把一个对象赋值给另一个对象时,我们将两个对象赋值给同一个引用。

Customer customer1 = new Customer { ID = 1, Name = "Test", City = "City", Sales = 1000m };
Customer customer2 = customer1;

Customer1Customer2 被链接,并且一个对象中的任何修改都将反映在另一个对象中。

克隆对于取消对象及其虚拟副本的链接是必要的,并且它们是独立的对象。

Customer customer2 = (Customer)customer1.Clone();

示例类

这是我们将用于示例的类

public class Customer : ICloneable
{
    public    int                ID        { get; set; }
    public    string             Name      { get; set; }
    public    decimal            Sales     { get; set; }
    public    DateTime           EntryDate { get; set; }
    public    Address            Adress    { get; set; }
    public    Collection<string> Mails     { get; set; }

    protected string             Data1     { get; set; }
    private   string             Data2     { get; set; }

    public Customer()
    {
        Data1 = "data1";
        Data2 = "Data2";
    }


    public virtual object Clone() { }
}

ICloneable

它是用于克隆对象的官方 .NET Framework 接口。 它非常简单,只有一个方法 Clone

这个接口让您可以随意使用 Clone 方法。 我们可以应用我们选择的任何深度级别。

public interface ICloneable
{
    object Clone();
}

这个 interface 最大的问题是 Clone 方法的返回值,对象类型。 每当您使用 Clone 方法时,您都必须进行强制转换为主要类型

Customer customer2 = (Customer)customer1.Clone();

扩展方法

克隆对象的另一种方法是使用扩展方法。 这些方法提供了返回泛型类型的机会,这样我们就可以避免装箱/拆箱问题。 我们只编写一次扩展方法,并且我们的扩展方法扩展了对象,我们可以将其用于所有 .NET 类型。

public static class MyExtensions
{
    public static T CloneObject<T>(this object source)
    {
        T result = Activator.CreateInstance<T>();

        //// **** made things

        return result;
    }
}

呼叫

Customer Customer2 = customer1.CloneObject();

我们可以将扩展方法与 ICloneable 结合使用

public class Customer : ICloneable
{
   // Properties ...

    public virtual object Clone()
    {
        return this.CloneObject();
    }
}

Object.MemberWiseClone

MemberWiseClone 是对象的 protected 方法。 此方法创建当前对象到新对象的浅表副本。

MemberWiseClone 以不同的方式复制引用属性(类)或值属性(结构)

  • 结构 - 逐位复制属性的值
  • – 复制属性的引用,因此它们是相同的对象

类的情况是一个问题,因为两个对象是相同的。 这是该方法的缺点。

MemberWiseClone 的使用通常与 ICloneable Interface 相同,因为 MemberWiseClone 是一个 protected 方法,并且必须在内部调用它。

MemberWiseClone 示例

public class Customer : ICloneable
{
    public    int                ID        { get; set; }
    public    string             Name      { get; set; }
    public    decimal            Sales     { get; set; }
    public    DateTime           EntryDate { get; set; }
    public    Address            Adress    { get; set; }
    public    Collection<string> Mails     { get; set; }

    protected string             Data1     { get; set; }
    private   string             Data2     { get; set; }

    public Customer()
    {
        Data1 = "data1";
        Data2 = "Data2";
    }

    public virtual object Clone()
    {
        return this.MemberwiseClone();
    }
}

优点

  • 易于开发
  • 代码量很少
  • 易于理解
  • 它复制任何字段/属性类型(简单和复杂)
  • 它不需要用任何特殊属性标记类

缺点

  • 它只能在类内部调用,因为它是一个 protected 方法。
  • 必须在所有要克隆的类中实现它。
  • 要克隆的对象的引用属性不会被复制,它们是被链接的。
  • clone 方法返回对象,因此每次使用它时,我们都必须进行强制转换。

如果我们尝试完全深度复制,我们必须手动分配所有引用属性

public virtual object Clone()
{
    var result = this.MemberwiseClone();

    // Manual assignments 

    result.Adress = new Address
    {
        City    = this.Adress.City,
        Street  = this.Adress.Street,
        ZipCode = this.Adress.ZipCode
    };

    result.Mails = new Collection<string>();

    this.Mails.ToList().ForEach(a => result.Mails.Add(a));

    return result;
}

流 - 格式化程序

这种克隆类型使用序列化来处理对象副本。 它进行深度复制,但强制使用任何序列化属性标记类对象。

在这个站点中,我们的同伴 Surajit Datta 有一个很好的例子 文章,我们将使用此代码作为我们的示例。

为了避免在所有克隆类中编写此代码,我们将创建一个扩展方法

public static T CloneObjectSerializable<T>(this T obj) where T : class
{
    MemoryStream    ms = new MemoryStream();
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(ms, obj);
    ms.Position = 0;
    object result = bf.Deserialize(ms);
    ms.Close();
    return (T) result;
}

呼叫

Customer customer2 = customer1.CloneObjectSerializable();

如果您运行此代码,它会抛出 SerializationException

为了防止这些错误,我们用 Serialization 属性标记该类

[Serializable]
public class Customer

优点

  • 易于开发
  • 易于在扩展方法中编写,因此我们只需实现一次
  • 它复制任何字段/属性类型(简单和复杂)
  • 它实现深度复制
  • 它不需要在类内部调用,因为它是对象扩展方法
  • 返回泛型类型,因此我们不必应用装箱/拆箱

缺点

  • 它需要用特殊属性标记
  • 对于您的实现,它需要更多的代码和逻辑
  • 内存泄漏(感谢 deverton bezerra

此方法可以与 ICloneable 完美配合使用,并保持其所有优点。

public virtual object Clone()
{
    return this.CloneObjectSerializable();
}

结论

在 .NET Framework 中没有克隆对象的魔法方法,但这两种模型使工作更容易。 在开发世界中,必须清楚地了解克隆对象。 这种误解通常是我们程序中出现错误和意外行为的后果。

© . All rights reserved.