3

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

这是工作流程:

  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# 对象进行深度克隆,包括克隆整个对象图?

4

4 回答 4

6

如果不对所有需要克隆的数据对象显式实现 ICloneable,您将无法获得比通用二进制序列化更好的结果。另一种可能的途径是反射,但如果您正在寻找性能,您也不会对此感到满意。

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

于 2009-04-28T22:29:23.157 回答
1

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

其他选项:

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

2)不要记住原始状态,一旦发生任何变化就将对象标记为脏。然后从原始源重新加载对象进行比较。我敢打赌,您的对象更改的频率低于不更改的频率,甚至更不频繁地更改回相同的值。

于 2009-04-28T22:20:20.213 回答
1

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

于 2009-04-28T22:29:29.800 回答
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 类的受保护方法。这意味着您只能在类的代码中使用此方法,如上所示。这意味着您不能将它用于外部类。

因此,实现 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!
于 2013-04-09T13:15:43.063 回答