复制构造函数与Clone()

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

备注 :我在括号内写了“深”,因为我认为这是无关紧要的。 显然其他人不同意,所以我问复制构造函数/操作符/函数是否需要清楚它实现了哪个拷贝变体 。

你不应该从ICloneable派生。

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

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

您可以定义两个接口,一个使用通用参数来支持强types克隆,另一个不使用弱types克隆function来处理不同types的可复制对象的集合:

 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 框架devise指南”和Brad Abrams的博客中进行了讨论

(我想如果你正在编写一个应用程序(而不是一个框架/库),所以你可以确定你的团队之外没有人会调用你的代码,这并不重要,你可以指定一个语义“.net”的ICloneable接口,但是你应该确保这是有据可查的,并且在你的团队中得到很好的理解,我个人坚持框架指南。

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

正如其他人所说, ICloneable的问题是,它没有具体说明它是深层还是浅层的复制,这使得它几乎不可用,实际上很less使用。 它也返回object ,这是一个痛苦,因为它需要大量的铸造。 (虽然你特别提到了在这个问题中的类,在一个struct上实现ICloneable需要装箱。)

复制构造器也遭受ICloneable的一个问题。 拷贝构造函数是做深层还是浅层拷贝并不明显。

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

最好创build一个DeepClone()方法。 这样的意图是非常清楚的。

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

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

要么

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

有时候我更喜欢静态版本,只是因为克隆似乎是对某个对象做的事情,而不是对象正在做的事情。 在任何一种情况下,克隆属于inheritance层次结构的对象时都会遇到问题,以及如何解决这些问题可能会最终推动devise。

 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(); } } 

我build议在克隆方法上使用复制构造函数,主要是因为克隆方法会阻止您只是使用构造函数进行readonly字段。

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

如果你需要不止一种拷贝(例如:deep / shallow),你可以在拷贝构造函数中用一个参数来指定它,尽pipe根据我的经验,我发现通常需要深度和浅度拷贝的混合。

例如:

 public class BaseType { readonly int mBaseField; public BaseType(BaseType pSource) { mBaseField = pSource.mBaseField; } public virtual BaseType Clone() { return new BaseType(this); } } public class SubType : BaseType { readonly int mSubField; public SubType(SubType pSource) : base(pSource) { mSubField = pSource.mSubField; } public override BaseType Clone() { return new SubType(this); } } 

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

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

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

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

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

不要陷入其他答案中明确的深浅论证中。 在现实世界中,它几乎总是中间的东西 – 无论哪种方式,都不应该成为来电者的担忧。

克隆()合同简直是“不会改变,当我改变第一个”。 你需要复制多less图,或者如何避免无限recursion来实现这一点,应该不涉及调用者。

你会遇到复制构造函数和抽象类的问题。 想象一下,你想要做到以下几点:

 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(); bX = 1; bY = 2; list.Add(b); C c = new C(); cX = 3; cZ = 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()实现。

ICloneable的问题既有意图也有一致性。 这是否是一个深刻的或浅的副本是不明确的。 正因为如此,它可能从来没有用过这种或那种方式。

我没有发现一个公开的拷贝构造函数在这个问题上更清楚。

这就是说,我会介绍一个方法系统,为您工作,并传达意图(a'la有点自我logging)

如果你试图复制的对象是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 } 

它依赖于有问题的类的复制语义,你应该把自己定义为开发者。 select的方法通常基于该类的预期用例。 也许这将有助于实施这两种方法。 但是两者都有类似的缺点 – 他们所执行的复制方法并不完全清楚。 这应该在你的class级的文件中明确说明。

对我有:

 // 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(); 

看起来更清楚的意图声明。

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

  1. 那些明确支持深度克隆
  2. 那些成员克隆将作为深层次的克隆,但既没有也不需要明确的支持。
  3. 那些不能被有效深度克隆的地方,在成员克隆的地方会产生不好的结果。

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

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

不过,我认为有一个明确的模式会比没有好。

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

如果你仔细阅读所有有趣的答案和讨论,你可能还会问自己,你是如何复制这些属性的 – 他们全部是明确的,还是有更好的方法去做? 如果这是你剩下的问题,看看这个(在StackOverflow):

我怎么能“深深地”使用通用的扩展方法克隆第三方类的属性?

它描述了如何实现一个扩展方法CreateCopy() ,该方法CreateCopy()包含所有属性的对象的“深层”副本(不必手动复制属性)。