复制构造函数与克隆()

Dim*_* C. 107 c#

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

备注:我在括号内写了"深刻",因为我认为这是无关紧要的.显然其他人不同意,所以我问复制构造函数/操作符/函数是否需要明确它实现的副本变体.

Sim*_*ens 84

你不应该来自ICloneable.

原因是,当Microsoft设计.net框架时,他们从未指定Clone()方法是ICloneable应该是深度还是浅度克隆,因此接口在语义上会被破坏,因为调用者不会知道调用是深入还是浅层克隆对象.

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

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

public interface IDeepCloneable
{
    object DeepClone();
}
public interface IDeepCloneable<T> : IDeepCloneable
{
    T DeepClone();
}
Run Code Online (Sandbox Code Playgroud)

然后你会像这样实现:

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

一般来说,我更喜欢使用描述的接口而不是复制构造函数,它使目的非常清晰.复制构造函数可能会被假定为深度克隆,但它肯定不像使用IDeepClonable接口那样明确.

这在.net 框架设计指南Brad Abrams博客中进行了讨论

(我想如果你正在编写一个应用程序(而不是框架/库),那么你可以确定你团队之外没有人会调用你的代码,这并不重要,你可以分配一个语义含义对.net ICloneable接口进行"深度克隆",但是你应该确保在你的团队中有很好的文档记录并且很好理解.我个人也坚持框架指南.)

  • 如果您要使用接口,那么如何使用DeepClone(T)()和DeepClone(T)(虚拟为T),两者都返回T?后一种语法将允许基于参数推断T. (2认同)
  • 题!在什么情况下你会想要非通用版本?对我来说,只有`IDeepCloneable&lt;T&gt;` 存在是有意义的,因为......如果你自己实现,你确实知道T是什么,即`SomeClass : IDeepCloneable&lt;SomeClass&gt; { ... }` (2认同)
  • @Kyle说你有一个采用可克隆对象的方法,`MyFunc(IDeepClonable data)`,然后它可以在所有clonables上工作,而不仅仅是特定的类型.或者如果你有一个可克隆的集合.`IEnumerable <IDeepClonable> lotsOfCloneables`然后你可以同时克隆大量的对象.如果你不需要那种东西,那么就把非通用的东西留下来. (2认同)

Jef*_*dge 31

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

这个问题ICloneable是,正如其他人所提到的,它并没有指定它是一个深或浅拷贝,这使得它几乎unuseable,并在实践中,很少使用.它也会返回object,这是一种痛苦,因为它需要大量的铸造.(虽然你特意在问题中提到的类,实施ICloneablestruct需要拳击.)

复制构造器也遇到ICloneable的一个问题.复制构造函数是执行深层还是浅层复制并不明显.

Account clonedAccount = new Account(currentAccount); // Deep or shallow?
Run Code Online (Sandbox Code Playgroud)

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

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

Account clonedAccount = currentAccount.DeepClone();  // instance method
Run Code Online (Sandbox Code Playgroud)

要么

Account clonedAccount = Account.DeepClone(currentAccount); // static method
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我不打算在这里论证这些要点,但在我看来,当他们打电话给Clone()时,来电者不应该太在意深浅.他们应该知道他们正在获得一个没有无效共享状态的克隆.例如,在深度克隆中,我可能不希望深度克隆每个元素.克隆的调用者应该关心的是,他们正在获得一个新的副本,该副本没有任何无效和不受支持的原始引用.调用方法'DeepClone'似乎向调用者传达了太多的实现细节. (10认同)
  • @KenBeckett - 我觉得克隆是对某个对象所做的事情的原因是因为一个对象应该"做一件事,做得好".通常,复制自身并不是一个类的核心竞争力,而是一个强大的功能.克隆一个BankAccount是你很可能想做的事情,但制作自己的克隆并不是银行账户的一个特征.你的细胞实例并不具有广泛的指导意义,因为繁殖正是细胞进化所要做的.Cell.Clone将是一个很好的实例方法,但对于大多数其他事情来说并非如此. (8认同)

Dav*_*eau 21

我建议在克隆方法上使用复制构造函数主要是因为克隆方法会阻止您创建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);
}
Run Code Online (Sandbox Code Playgroud)

  • +1用于解决多态克隆问题; 克隆的重要应用. (8认同)

Dan*_*anO 17

有一个很好的论据,你应该使用受保护的拷贝构造函数实现clone()

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

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

(尽管推荐的公共接口是Clone()接口而不是基于Constructor的接口.)

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

克隆()合约只是"当我改变第一个时不会改变".您需要复制多少图形,或者如何避免无限递归才能实现这一点,这与调用者无关.

  • 列表<T>过于通用(哈哈),克隆甚至有意义.在您的情况下,这肯定只是列表的副本,而不是列表指向的对象.操作新列表不会影响第一个列表,但是对象是相同的,除非它们是不可变的,否则如果更改第二个集合中的那些,第一个集合中的那些将会改变.如果你的库中有list.Clone()操作,你应该期望结果是一个完整的克隆,因为"当我对第一个做某事时不会改变." 也适用于包含的对象. (3认同)

Gra*_*ton 12

实现ICloneable是不推荐的,因为没有指定它是深拷贝还是浅拷贝,所以我会去构造函数,或者只是自己实现一些东西.也许称之为DeepCopy()让它变得非常明显!

  • 我认为构造函数几乎和ICloneable接口一样不清楚 - 您必须阅读API文档/代码才能知道它是否进行深度克隆.我只是用`DeepClone()`方法定义一个`IDeepCloneable <T>`接口. (7认同)
  • @Grant,构造函数如何转发意图?IOW,如果一个对象在构造函数中占据了自己,那么副本是深还是浅?否则,我完全同意DeepCopy()(或其他)的建议. (5认同)
  • @Jon - 反应堆从未结束! (2认同)
  • 有没有人看过iCloneable用于未知类型的对象的用途?接口的全部意义在于它们可以用于未知类型的对象; 否则,也可以简单地使克隆成为返回有问题类型的标准方法. (2认同)

Mat*_*sen 11

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

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 基于接口的方法的良好论据. (2认同)