前几天我使用自定义方法深度克隆对象,我知道你可以用不同的方式深度克隆(反射,二进制序列化等),所以我只是想知道:
Microsoft在框架中不包含深层复制方法的原因是什么?
答案 0 :(得分:8)
问题比你想象的要困难得多,至少在一般情况下是这样。
对于初学者来说,副本不仅仅是深或浅,而是一个频谱。
让我们想象一下,我们有一个字符串数组列表,我们想要复制它。
我们从最浅层开始,我们只是将整个事物的引用复制到另一个变量。对另一个变量引用的列表的任何更改都会被另一个看到。
所以现在我们去创建一个全新的列表来给第二个变量。对于第一个列表中的每个项目,我们将其添加到第二个列表中。现在我们可以修改从任一变量引用的列表,而不会被另一个变量看到。但是,如果我们抓住列表的第一项并更改第一个数组中的字符串,那么两个列表都会看到它!
现在我们正在创建一个新列表,对于第一个列表中的每个数组,我们创建一个新数组,将基础数组中的每个字符串添加到新数组中,然后添加每个新数组都列入新列表。现在我们可以改变任一列表中的任何数组而不会看到更改。但是等等,两个列表仍然引用相同的字符串(毕竟它们是值类型;它们内部有一个字符数组用于它们的数据)。如果一些卑鄙的人出现并改变其中一个字符串(使用不安全的代码,你实际上可以这样做)会怎么样?所以现在你要用深拷贝复制所有字符串。但是,如果我们不需要那样做呢?如果我们知道没有人会这么说他们会改变字符串怎么办?或者,就此而言,如果我们知道没有任何数组将被突变(或者如果它们将被突变,那么它们假定将被两个列表反映出来)。
当然还有诸如循环引用之类的问题,类中没有真正代表它的状态的字段(即可能只是重新计算的缓存值或派生数据 - 克隆需要的。)
实际上你需要拥有每个类型的工具IClonable
或一些等价物,并拥有自己的自定义代码来克隆自己。这将是为语言维护的很多工作,特别是因为有很多方法可以克隆复杂对象。成本会非常高,并且(在少数几个被认为值得实现克隆方法的对象之外)的好处通常是不值得的。作为程序员,您可以根据自己知道的深度来编写自己的逻辑来克隆类型。
答案 1 :(得分:5)
它与C和C ++中的工作方式(或不工作方式)类似:
要进行深层复制,您实际上必须知道如何解释不同的数据。在普通情况下,浅拷贝(提供)与深拷贝相同。但是,一旦这不再是真的,它实际上取决于实施和解释。没有一般的经验法则。
让我们以游戏为例:
我可以想到两种可能的解决方案:
UniqueID
不能复制,但同时您无法定义如何发生这种情况。即使你可以,你也可以...... CopyTo()
)。答案 2 :(得分:0)
A)因为很少你想让副本真的很深
B)因为框架不能保证知道如何真正有意义地克隆一个对象
C)因为以一种简洁的方式实现深度克隆很简单,并且使用反射和递归采用一种方法和几行代码
但我会尝试找一篇涵盖该
的旧MSDN文章编辑:我没有找到:(我仍然确定我在某个地方看到了它,但我现在不能google-it-out ..但有关ICloneable和派生的一些有用的链接:
http://blogs.msdn.com/b/brada/archive/2004/05/03/125427.aspx http://blogs.msdn.com/b/mrtechnocal/archive/2009/10/19/why-not-icloneable-t.aspx https://stackoverflow.com/a/3712564/717732
所以,由于我没有找到作者的话,让我扩大一点:
答:因为很少你想让副本真的很深
你知道,框架怎么能猜出它一般有多深?让我们假设完全深入,让我们假设它已经实现了。现在我们有成员克隆和全克隆方法。但是,在某些情况下,人们需要克隆我,但不是根基。因此,他们发布了另一个问题,为什么总克隆无法切断原始基础。或者是第二次原始的。等等。提供深度克隆几乎没有解决.Net团队的观点,因为我们,用户,仍然会因为我们看到一些部分工具并且懒惰而想要拥有一切而咆哮:)
B)因为框架无法保证知道如何真正有意义地克隆一个对象
特别是对于一些带有句柄或类似本机ID的特殊对象,例如来自Entity Framework,.Net Remoting Proxies,COM-wrappers等的特殊对象:您可能成功地读取并克隆了上层的层次结构层,但最终,在下面的某处找到像IntPtr
这样的一些神秘的东西你只是知道你不应该复制。大部分的时间。但有时你可以。但框架的代码必须是通用的。深度克隆要么必须严格复杂,要么对特殊的类成员进行许多健全性检查,否则如果程序员在具有程序员不关心的基类的东西上调用它,就会产生危险的结果。
B +)另外,请注意,树中的基类越多,它们就越可能有一些参数化构造函数,这可能表明直接复制不是一个好主意。直接可复制类通常具有无参数构造函数和属性可访问的所有可复制数据。
B ++)从框架的设计者角度来看,考虑到内存和速度问题,浅层复制几乎总是非常快,而深度复制恰恰相反。不允许开发人员自由地深层复制大型对象,这对框架和平台的声誉是有益的。无论如何,如果你的对象轻巧简单,你需要一个深层拷贝吗? :)不提供深层复制可以鼓励开发人员考虑深度复制的需要,通常会使应用程序更轻松,更快。
C)因为以一种简洁的方式实现深度克隆很简单,并且使用反射和递归采用一种方法和几行代码
有一个浅表副本,实际写一个深拷贝有多难?不是那么难!只需实现一个给定对象'obj'的方法:
pseudocode:
object deepcopier(object obj)
newobject = obj.shallowcopy()
foreach(field in newobject.fields)
newobject.field = deepcopier(newobject.field)
return newobject
而且,就是这样。当然,字段枚举必须由Reflection执行,并且还要读/写字段。
然而,这种方式非常有说服力。这有一个严重的缺陷:如果某个对象有两个字段指向同一个另一个对象怎么办?我们应该检测它并进行克隆一次,然后将两个字段分配给该克隆。此外,如果某个字段指向的对象引用了某个也被另一个对象(...)指向的对象 - 也可能只需要跟踪和克隆一次。还有,周期怎么样?如果在树的深处,某个对象有一个返回根的引用?像上面这样的算法会很高兴地下降并且会再次重新复制所有内容,然后再次复制,最终会被StackOverflow阻塞。
这使得克隆很难被跟踪并且开始看起来更像序列化。实际上,如果您的类是DataContract或Serializable,您只需序列化它并反序列化以获得完美的深层副本:)
深度克隆很难以通用的方式进行,除非您知道对象的含义及其所有字段的含义,并知道哪些应该真正克隆,哪些应该统一。如果您作为开发人员知道这只是一个非常安全的深度克隆数据对象,那么whydontya只是让它可以序列化?如果你不能使它Serializable,那么你可能也无法深度克隆它!