高效克隆缓存的对象

时间:2009-04-28 22:06:03

标签: c# performance clone deep-copy

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

这是工作流程:

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

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

4 个答案:

答案 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!