【发布时间】:2010-11-21 11:35:03
【问题描述】:
过去我需要克隆对象,结果发现它们没有实现Clone()方法,迫使我手动(创建一个新实例并将所有属性从原始实例复制到新的)
为什么克隆不像复制分配对象的内存块那么容易,从而在object 类中拥有Clone 方法,让.NET 中的所有类都继承它?
【问题讨论】:
过去我需要克隆对象,结果发现它们没有实现Clone()方法,迫使我手动(创建一个新实例并将所有属性从原始实例复制到新的)
为什么克隆不像复制分配对象的内存块那么容易,从而在object 类中拥有Clone 方法,让.NET 中的所有类都继承它?
【问题讨论】:
因为这不会执行深度克隆,而这通常是真正需要的克隆。想象一下,您有一个对数组或列表的引用……只需复制 您的对象 占用的内存即可克隆该引用。对数组的任何更改都将通过克隆以及原始对象可见 - 因此两个对象仍然连接,这违反了正常的克隆点。
如果您想完全实现该功能,这很容易 - 这就是 Object.MemberwiseClone() 的用途。大多数时候,如果克隆一个对象甚至是有意义的(克隆的NetworkStream 是什么意思?)克隆每个属性是有意义的......除非它已经引用了一个不可变的值等。换句话说,这是一个自然难题,这就是为什么大多数类型不支持克隆的原因。
如果您尽可能坚持使用不可变类型,这并不是什么大问题...诚然,这会让其他事情变得更加困难,但在许多情况下它可以非常强大。
【讨论】:
DeeplyCloneable 接口(粗略的想法),难道不能有一个方法DeepClone 进行深度克隆吗?显然这会影响到许多类,而且克隆可能不是那么受欢迎的活动。
有一个像Object.MemberwiseClone(), 这样的东西,它可以按照您的描述进行操作。它正在制作对象的浅拷贝。
它不会自动进行深度克隆...您需要手动在所有成员对象上调用克隆,等等。
【讨论】:
您必须在您的类中显式实现ICloneable 接口。
但是,正如文档所述,MemberwiseClone 中的克隆机制不区分浅拷贝和深拷贝。
【讨论】:
其他人已经解释了MemberwiseClone,但没有人解释为什么它受到保护。我会尽量给出理由。
这里的问题是MemberwiseClone 只是盲目地复制状态。在许多情况下,这是不可取的。例如,该对象可能有一个私有字段,它是对List 的引用。浅拷贝,例如 MemberwiseClone 所做的,会导致新对象指向同一个列表 - 并且编写的类可能不会期望与其他任何人共享该列表。
或者一个对象可以有某种 ID 字段,在构造函数中生成 - 同样,当你克隆它时,你会得到两个具有相同 ID 的对象,这可能会导致假设 ID 是唯一的方法中的各种奇怪的失败.
或者说你有一个对象可以打开一个套接字或一个文件流,并存储一个对它的引用。 MemberwiseClone 只会复制引用 - 你可以想象两个对象试图交错调用同一流的结果不会很好。
简而言之,“克隆”不是针对任意对象的明确定义的操作。在 C++ 中默认为所有类提供 memberwise operator= 的事实更令人讨厌,因为人们经常忘记它的存在,并且不要为复制没有意义的类禁用它,或者危险的(而且有很多这样的课程)。
【讨论】:
有(至少)两种克隆。大多数参考文献都在讨论浅和深克隆,但实际上两者之间存在阴影。
关键问题是“应该复制多少”和“应该分享多少”之间的张力。
考虑一个Order 对象,其中包含对Customer、Address 和List 的引用OrderLines。
如果你想Clone() 和Order,涉及什么?
“只是复制内存块”会给你一个新的Order,但是一个共享Customer、Address和List的OrderLines。 (请记住,对象成员是通过引用存储的,因此当您复制内存块时,最终会得到对同一对象的两个引用)。
显然,您不想在两个Orders 之间共享OrderLines 的List。事实上,您可能还想克隆每个OrderLine。
如果您使用通用的Clone() 方法,该方法如何知道哪些成员应该递归克隆,哪些不应该?
一般来说,这是一个棘手的问题 - 这就是为什么要由单个对象来实现适合其情况的语义。
最后一点:即使我确实创建了Clone() 对象的能力,我也不倾向于创建Clone() 方法,而是更喜欢复制构造函数 - 一个接受的构造函数另一个对象作为基础。如果找不到Clone(),请查找。
【讨论】: