高效克隆缓存的对象

我们有一个应用程序,它对数据对象进行比较,以确定对象的一个​​版本是否与另一个版本不同。 我们的应用程序也对这些对象进行了一些广泛的缓存,在进行这些比较时我们遇到了一些性能问题。

这是工作流程:

  1. 数据项1是内存中的当前项。 此项最初是从缓存中检索并进行深度克隆(所有子对象,如字典等)。 然后编辑数据项1,并修改其属性。
  2. 然后,我们将此对象与存储在缓存中的原始版本进行比较。 由于数据项1已克隆且其属性已更改,因此这些对象应该不同。

这里有几个问题。

主要问题是我们的深度克隆方法非常昂贵。 我们对浅色克隆进行了分析,速度慢了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#对象上进行深度克隆,这将克隆整个对象图?

如果没有在需要克隆的所有数据对象上明确地实现ICloneable,那么您将无法比通用二进制序列化更好。 另一种可能的路线是reflection,但如果您正在寻找性能,您将不会满意。

我会考虑使用ICloneable进行深度复制和/或IComparable的命中,以便比较对象是否不同……如果性能对您来说是个大问题。

也许你不应该深刻克隆呢?

其他选择:

1)让你的“缓存”对象记住它的原始状态,并在每次发生任何变化时让它更新“更改”标志。

2)不记得原始状态,只要有任何变化,就把对象标记为脏。 然后从原始源重新加载对象进行比较。 我敢打赌,你的对象变化的频率低于不变化的频率,甚至更少的频率变回相同的值。

我的回复可能不适用于您的案例,因为我不知道您的限制和要求是什么,但我觉得通用克隆可能会有问题。 正如您已经遇到的那样,性能可能是个问题。 有些东西需要在对象图中识别唯一的实例,然后创建一个精确的副本。 这就是二进制序列化器为您所做的事情,但它也做得更多(序列化本身)。 看到它比你想象的要慢,我并不感到惊讶。 我有类似的经验(顺便提一下也与缓存有关)。 我的方法是自己实施克隆; 即为实际需要克隆的类实现IClonnable。 您正在缓存的应用程序中有多少个类? 如果有太多(手动编写克隆代码),考虑一些代码生成是否有意义?

您可以通过两种方式进行深度克隆:通过实现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!