我们有一个应用程序对数据对象进行比较,以确定对象的一个版本是否与另一个版本不同。我们的应用程序还对这些对象进行了一些广泛的缓存,在进行这些比较时,我们遇到了一些性能问题。
这是工作流程:
这里有几个问题。
主要问题是我们的深度克隆方法非常昂贵。我们对浅色克隆进行了分析,速度慢了10倍。那是废话。这是我们深度克隆的方法:
public object Clone()
{
using (var memStream = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
binaryFormatter.Serialize(memStream, this);
memStream.Seek(0, SeekOrigin.Begin);
return binaryFormatter.Deserialize(memStream);
}
}
我们最初使用以下内容进行克隆:
public object Clone()
{
return this.MemberwiseClone();
}
这是更高效的,但是因为它做了一个浅层克隆,所有作为该对象属性的复杂对象(如字典等)都没有被克隆。该对象仍将包含与缓存中对象相同的引用,因此在比较时属性将相同。
那么,是否有人有一种有效的方法在C#对象上进行深度克隆,这将克隆整个对象图?
答案 0 :(得分:6)
如果没有在需要克隆的所有数据对象上明确地实现ICloneable,那么您将无法比通用二进制序列化更好。另一种可能的途径是反思,但如果你正在寻找表现,你将不会满意。
我会考虑使用ICloneable进行深度复制和/或IComparable的命中,以便比较对象是否不同......如果性能对你来说是个大问题。
答案 1 :(得分:1)
也许你不应该深刻克隆呢?
其他选择:
1)让你的“缓存”对象记住它的原始状态,并在每次发生任何变化时使 it 更新“更改”标记。
2)不记得原始状态,只要有任何变化,就将对象标记为脏。然后从原始源重新加载对象进行比较。我敢打赌,你的对象变化的频率低于不变化的频率,甚至更少的频率变回相同的值。
答案 2 :(得分:1)
我的回复可能不适用于您的案例,因为我不知道您的限制和要求是什么,但我觉得通用克隆可能会有问题。正如您已经遇到的那样,性能可能是个问题。有些东西需要在对象图中识别唯一的实例,然后创建一个精确的副本。这就是二进制序列化器为您所做的事情,但它也做得更多(序列化本身)。看到它比你想象的要慢,我并不感到惊讶。我有类似的经历(顺便提一下也与缓存有关)。我的方法是自己实施克隆;即为实际需要克隆的类实现IClonnable。您正在缓存的应用程序中有多少个类?如果有太多(手动编写克隆代码),考虑一些代码生成是否有意义?
答案 3 :(得分:0)
您可以通过两种方式进行深度克隆:通过实现ICloneable(并调用Object.MemberwiseClone方法)或通过二进制序列化。
第一种方式
第一种(可能更快,但并非总是最好)的方法是在每种类型中实现ICloneable接口。以下示例说明。 C类实现了ICloneable,并且因为这个类引用了其他类D和E,所以后者也实现了这个接口。在C的Clone方法中,我们调用其他类型的Clone方法。
Public Class C
Implements ICloneable
Dim a As Integer
' Reference-type fields:
Dim d As D
Dim e As E
Private Function Clone() As Object Implements System.ICloneable.Clone
' Shallow copy:
Dim copy As C = CType(Me.MemberwiseClone, C)
' Deep copy: Copy the reference types of this object:
If copy.d IsNot Nothing Then copy.d = CType(d.Clone, D)
If copy.e IsNot Nothing Then copy.e = CType(e.Clone, E)
Return copy
End Function
End Class
Public Class D
Implements ICloneable
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone()
End Function
End Class
Public Class E
Implements ICloneable
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone()
End Function
End Class
现在,当您为C实例调用Clone方法时,您将获得该实例的深度克隆:
Dim c1 As New C
Dim c2 As C = CType(c1.Clone, C) ' Deep cloning. c1 and c2 point to two different
' locations in memory, while their values are the
' same at the moment. Changing a value of one of
' these objects will NOT affect the other.
注意:如果D类和E类具有引用类型,则必须像对C类一样实现它们的Clone方法。依此类推。
警告: 1 - 只要没有循环引用,上面的示例就是有效的。例如,如果类C具有自引用(例如,类型为C的字段),则实现ICloneable接口并不容易,因为C中的Clone方法可能进入无限循环。
2 - 需要注意的另一点是,MemberwiseClone方法是Object类的Protected方法。这意味着您只能在类的代码中使用此方法,如上所示。这意味着你不能将它用于外部类。
因此,仅当上述两个警告不存在时,实施ICloneable才有效。否则,您应该使用二进制序列化技术。
第二种方式
二进制序列化可用于深度克隆而不会出现上面列出的问题(尤其是循环引用)。这是一个使用二进制序列化执行深度克隆的通用方法:
Public Class Cloning
Public Shared Function DeepClone(Of T)(ByVal obj As T) As T
Using MStrm As New MemoryStream(100) ' Create a memory stream.
' Create a binary formatter:
Dim BF As New BinaryFormatter(Nothing, New StreamingContext(StreamingContextStates.Clone))
BF.Serialize(MStrm, obj) ' Serialize the object into MStrm.
' Seek the beginning of the stream, and then deserialize MStrm:
MStrm.Seek(0, SeekOrigin.Begin)
Return CType(BF.Deserialize(MStrm), T)
End Using
End Function
End Class
以下是使用此方法的方法:
Dim c1 As New C
Dim c2 As C = Cloning.DeepClone(Of C)(c1) ' Deep cloning of c1 into c2. No need to
' worry about circular references!