高效克隆缓存对象

5
我们有一个应用程序,用于比较数据对象以确定一个版本的对象是否与另一个版本不同。我们的应用程序还对这些对象进行了广泛的缓存,当涉及到执行这些比较时,我们遇到了一些性能问题。
以下是工作流程:
1. 数据项1是内存中的当前项目。此项目最初从缓存中检索并进行深度克隆(所有子对象,如字典等)。然后编辑数据项1,并修改其属性。 2. 然后,我们将此对象与存储在缓存中的原始版本进行比较。由于已克隆Data item 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#对象进行深层克隆,以覆盖整个对象图的克隆?


假设您想要通用的Clone()方法,因为您不想在每个对象上实现ICloneable接口? - Chad Grant
这只是克隆一个特定的对象。这个对象是我们应用程序中的核心数据对象。这回答了你的问题吗? - steve_c
4个回答

6

如果你想要进行深度复制,除非在所有需要被克隆的数据对象上明确实现ICloneable接口,否则你不会比使用通用二进制序列化方式获得更好的效果。另一个可能的方法是使用反射,但是如果你在寻求性能方面的优化,你也不会感到满意。

如果性能确实是大问题,我建议采用ICloneable接口进行深拷贝和/或IComparable接口进行比较。


2

也许您不需要进行深度克隆?

其他选项:

1)让您的“缓存”对象记住其原始状态并使其在任何更改时更新“已更改”标志。

2)不要记住原始状态,只要有任何更改就将对象标记为脏。然后从原始源重新加载对象以进行比较。我敢打赌,您的对象更改的频率要少于不更改的频率,而且很少更改回相同的值。


1
DirtyFlag?那是一堆工作,而且你会失去自动属性,代码中充斥着SetIsDirty(),很容易忘记设置标志(容易产生错误)。他通过实现IComparable与排序等功能可以获得更多的好处... - Chad Grant
除非您使用PostSharp,否则您无需更改任何内容,即可获得所有PropertyChanged的好处,并且您可以将其用于更多方面。 - MBoros

1

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


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!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接