复制构造函数vs Clone()

时间:2010-07-27 15:45:42

标签: c#

在C#中,向类中添加(深层)复制功能的首选方法是什么?是应该实现复制构造函数,还是从ICloneable派生并实现Clone()方法?

备注:我在括号内写了“深”,因为我认为这是无关紧要的。显然其他人不同意,所以我问whether a copy constructor/operator/function needs to make clear which copy variant it implements

11 个答案:

答案 0 :(得分:82)

您不应该从ICloneable派生。

原因是,当Microsoft设计.net框架时,他们从未指定Clone()上的ICloneable方法是深层还是浅层克隆,因此当您的来电者赢了时,界面会在语义上被破坏'知道该调用是深会还是浅点克隆该对象。

相反,您应该使用IDeepCloneable(和IShallowCloneable)方法定义自己的DeepClone()(和ShallowClone())接口。

您可以定义两个接口,一个用于支持强类型克隆的通用参数,另一个用于在处理不同类型的可克隆对象的集合时不保留弱类型克隆功能:

public interface IDeepCloneable
{
    object DeepClone();
}
public interface IDeepCloneable<T> : IDeepCloneable
{
    T DeepClone();
}

然后你会这样实现:

public class SampleClass : IDeepCloneable<SampleClass>
{
    public SampleClass DeepClone()
    {
        // Deep clone your object
        return ...;
    }
    object IDeepCloneable.DeepClone()   
    {
        return this.DeepClone();
    }
}

通常我更喜欢使用所描述的接口而不是复制构造函数,它使目的非常清晰。复制构造函数可能被认为是一个深度克隆,但它肯定不像使用IDeepClonable接口那么明确。

这将在.net Framework Design GuidelinesBrad Abrams'博客

中讨论

(我想如果你正在编写一个应用程序(而不是框架/库),那么你可以确定你团队之外的任何人都不会调用你的代码,这对你来说并不重要。可以为.net ICloneable接口分配“deepclone”的语义含义,但是你应该确保在你的团队中有很好的文档和很好的理解。我个人也坚持框架指南。)

答案 1 :(得分:31)

  

在C#中,向类中添加(深层)复制功能的首选方法是什么?   应该实现复制构造函数,   或者更确切地说是从IClo​​neable派生并实现Clone()方法?

正如其他人所提到的,ICloneable的问题在于它没有指定它是深层还是浅层副本,这使得它实际上不可用,并且在实践中很少使用。它也会返回object,这很痛苦,因为它需要大量的投射。 (尽管您在问题中特别提到了类,但在ICloneable上实施struct需要装箱。)

复制构造器也遇到ICloneable的一个问题。复制构造函数是做深度还是浅度复制并不明显。

Account clonedAccount = new Account(currentAccount); // Deep or shallow?

最好创建一个DeepClone()方法。这样的意图非常明确。

这引出了一个问题,即它应该是静态方法还是实例方法。

Account clonedAccount = currentAccount.DeepClone();  // instance method

Account clonedAccount = Account.DeepClone(currentAccount); // static method

我有时稍微喜欢静态版本,只是因为克隆似乎是对象所做的事情,而不是对象正在做的事情。在任何一种情况下,克隆作为继承层次结构一部分的对象时都会遇到问题,以及这些问题如何交叉可能最终推动设计。

class CheckingAccount : Account
{
    CheckAuthorizationScheme checkAuthorizationScheme;

    public override Account DeepClone()
    {
        CheckingAccount clone = new CheckingAccount();
        DeepCloneFields(clone);
        return clone;
    }

    protected override void DeepCloneFields(Account clone)
    {
        base.DeepCloneFields(clone);

        ((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone();
    }
}

答案 2 :(得分:20)

我建议在克隆方法上使用复制构造函数主要是因为克隆方法会阻止您创建字段readonly,如果您使用了构造函数,则可能会这样做。

如果您需要多态克隆,则可以将abstractvirtual Clone()方法添加到您通过调用复制构造函数实现的基类。

如果您需要多种副本(例如:深/浅),您可以在复制构造函数中使用参数指定它,尽管根据我的经验,我发现通常我需要深层和浅层复制的混合

例如:

public class BaseType {
   readonly int mBaseField;

   public BaseType(BaseType pSource) =>
      mBaseField = pSource.mBaseField;

   public virtual BaseType Clone() =>
      new BaseType(this);
}

public class SubType : BaseType {
   readonly int mSubField;

   public SubType(SubType pSource)
   : base(pSource) =>
      mSubField = pSource.mSubField;

   public override BaseType Clone() =>
      new SubType(this);
}

答案 3 :(得分:15)

有一个很好的论据,你应该implement clone() using a protected copy constructor

  

最好提供受保护(非公共)的复制构造函数,并从clone方法中调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并使用受保护的拷贝构造函数安全地创建对象。

所以这不是一个“对抗”的问题。您可能需要复制构造函数和克隆接口才能正确执行。

(虽然推荐的公共接口是Clone()接口,而不是基于Constructor的。)

不要陷入其他答案中明确的深层次或浅层次的争论中。在现实世界中,它几乎总是介于两者之间 - 无论哪种方式,都不应该是呼叫者的关注点。

Clone()合同简单地“当我改变第一个时不会改变”。你需要复制多少图表,或者你如何避免无限递归才能实现这一点,这不应该与调用者有关。

答案 4 :(得分:12)

Implementing ICloneable's not recommended由于没有指定它是深拷贝还是浅拷贝,所以我会去构造函数,或者只是自己实现一些东西。也许称之为DeepCopy()让它变得非常明显!

答案 5 :(得分:10)

您将遇到复制构造函数和抽象类的问题。想象一下,你想要做以下事情:

abstract class A
{
    public A()
    {
    }

    public A(A ToCopy)
    {
        X = ToCopy.X;
    }
    public int X;
}

class B : A
{
    public B()
    {
    }

    public B(B ToCopy) : base(ToCopy)
    {
        Y = ToCopy.Y;
    }
    public int Y;
}

class C : A
{
    public C()
    {
    }

    public C(C ToCopy)
        : base(ToCopy)
    {
        Z = ToCopy.Z;
    }
    public int Z;
}

class Program
{
    static void Main(string[] args)
    {
        List<A> list = new List<A>();

        B b = new B();
        b.X = 1;
        b.Y = 2;
        list.Add(b);

        C c = new C();
        c.X = 3;
        c.Z = 4;
        list.Add(c);

        List<A> cloneList = new List<A>();

        //Won't work
        //foreach (A a in list)
        //    cloneList.Add(new A(a)); //Not this time batman!

        //Works, but is nasty for anything less contrived than this example.
        foreach (A a in list)
        {
            if(a is B)
                cloneList.Add(new B((B)a));
            if (a is C)
                cloneList.Add(new C((C)a));
        }
    }
}

在完成上述操作后,您就开始希望您使用接口,或者为DeepCopy()/ ICloneable.Clone()实现安装。

答案 6 :(得分:4)

ICloneable的问题是意图和一致性。它无论是深层还是浅层都不清楚。因此,它可能从未以某种方式使用过。

我没有找到公共副本构造函数来更清楚。

那就是说,我会介绍一个适合你的方法系统并转发意图(a'la有点自我记录)

答案 7 :(得分:3)

如果您要复制的对象是Serializable,则可以通过序列化并反序列化来克隆它。然后,您不需要为每个类编写复制构造函数。

我现在无法访问代码,但它是这样的

public object DeepCopy(object source)
{
   // Copy with Binary Serialization if the object supports it
   // If not try copying with XML Serialization
   // If not try copying with Data contract Serailizer, etc
}

答案 8 :(得分:2)

它依赖于相关类的复制语义,您应该将自己定义为开发人员。选择的方法通常基于该类的预期用例。也许实现这两种方法都有意义。但两者都有类似的缺点 - 它们实施的复制方法并不十分清楚。这应该在您班级的文档中明确说明。

对我来说:

// myobj is some transparent proxy object
var state = new ObjectState(myobj.State);

// do something

myobject = GetInstance();
var newState = new ObjectState(myobject.State);

if (!newState.Equals(state))
    throw new Exception();

而不是:

// myobj is some transparent proxy object
var state = myobj.State.Clone();

// do something

myobject = GetInstance();
var newState = myobject.State.Clone();

if (!newState.Equals(state))
    throw new Exception();

看起来更明确的意图陈述。

答案 9 :(得分:0)

我认为可克隆对象应该有一个标准模式,但我不确定该模式到底应该是什么。关于克隆,似乎有三种类型:

  1. 明确支持深度克隆的那些
  2. 那些成员克隆将作为深度克隆工作,但既不具备也不需要明确支持的那些。
  3. 那些无法有效深度克隆的,以及成员克隆会产生不良结果的那些。

据我所知,获得与现有对象相同类的新对象的唯一方法(至少在.net 2.0中)是使用MemberwiseClone。一个漂亮的模式似乎是拥有一个“new”/“Shadows”函数Clone,它总是返回当前类型,其定义总是调用MemberwiseClone然后调用受保护的虚拟子例程CleanupClone(originalObject)。 CleanupCode例程应调用base.Cleanupcode来处理基类型的克隆需求,然后添加自己的清理。如果克隆例程必须使用原始对象,则必须进行类型转换,否则唯一的类型转换将在MemberwiseClone调用上。

不幸的是,上面类型(1)而不是类型(2)的类的最低级别必须被编码以假设其较低类型不需要任何明确的克隆支持。我真的没有看到任何方法。

尽管如此,我认为定义模式总比没有好。

顺便说一句,如果一个人知道一个人的基类型支持iCloneable,但不知道它使用的函数的名称,有没有办法引用一个基类型的iCloneable.Clone函数?

答案 10 :(得分:0)

如果您仔细阅读了所有有趣的答案和讨论,您可能仍然会问自己,您是如何复制属性的 - 这些属性都是明确的,还是有更优雅的方式来实现?如果那是你剩下的问题,请看一下(在StackOverflow上):

How can I “deeply” clone the properties of 3rd party classes using a generic extension method?

它描述了如何实现扩展方法CreateCopy(),该方法创建对象的“深层”副本,包括所有属性(无需手动复制属性)。