Pregunta

Tenemos una aplicación que realiza comparaciones en objetos de datos para determinar si una versión del objeto es diferente a otra. Nuestra aplicación también realiza un extenso almacenamiento en caché de estos objetos, y nos hemos encontrado con un pequeño problema de rendimiento cuando se trata de hacer estas comparaciones.

Aquí está el flujo de trabajo:

  1. El elemento de datos 1 es el elemento actual en la memoria. Este elemento se recuperó inicialmente de la memoria caché y se clonó en profundidad (todos los objetos secundarios, como los diccionarios, etc.). El elemento de datos 1 se edita y se modifican sus propiedades.
  2. Estamos comparando este objeto con la versión original que se almacenó en la memoria caché. Dado que el elemento de datos 1 se clonó y sus propiedades cambiaron, estos objetos deberían ser diferentes.

Hay un par de problemas aquí.

El problema principal es que nuestro método de clonación profunda es muy costoso. Lo perfilamos contra un clon superficial y fue 10 veces más lento. Eso es basura. Aquí está nuestro método de clonación profunda:

    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);
        }
    }

Inicialmente estábamos usando lo siguiente para clonar:

public object Clone()
{
    return this.MemberwiseClone();
}

Esto fue más eficaz, pero debido a que hace un clon superficial, no se clonaron todos los objetos complejos que eran propiedades de este objeto, como los diccionarios, etc. El objeto todavía contendría la misma referencia que el objeto que estaba en el caché, por lo que las propiedades serían las mismas en comparación.

Entonces, ¿alguien tiene una forma eficiente de hacer un clon profundo en objetos C # que cubran la clonación de todo el gráfico de objetos?

¿Fue útil?

Solución

No podrá obtener mucho mejor que su serialización binaria genérica sin implementar explícitamente IClonable en todos sus objetos de datos que deben clonarse. Otra posible ruta es la reflexión, pero tampoco estará satisfecho con ella si está buscando rendimiento.

Consideraría aceptar el impacto con ICloneable para copia profunda y / o IComparable para comparar si los objetos son diferentes ... si el rendimiento es un problema tan grande para usted.

Otros consejos

¿Quizás no deberías clonar en profundidad entonces?

Otras opciones:

1) Haz tu " caché " el objeto recuerda su estado original y hace que lo actualice " cambiado " marca cada vez que algo cambie.

2) No recuerde el estado original y solo marque el objeto como sucio una vez que algo haya cambiado. Luego vuelva a cargar el objeto de la fuente original para comparar. Apuesto a que sus objetos cambian con menos frecuencia que los que no cambian, y aún menos frecuentemente cambian de nuevo al mismo valor.

Es posible que mi respuesta no se aplique a su caso porque no sé cuáles son sus restricciones y requisitos, pero creo que una clonación de propósito general puede ser problemática. Como ya se ha encontrado, el rendimiento puede ser un problema. Algo necesita identificar instancias únicas en el gráfico de objetos y luego crear una copia exacta. Esto es lo que el serializador binario hace por usted, pero también hace más (la propia serialización). No me sorprende que ver que es más lento lo que esperabas. Tengo experiencia similar (por cierto también relacionada con el almacenamiento en caché). Mi enfoque sería implementar la clonación por mí mismo; es decir, implementar IClonnable para las clases que realmente necesitan ser clonadas. ¿Cuántas clases hay en tu aplicación que estás guardando? Si hay demasiados (para codificar manualmente la clonación), ¿tendría sentido considerar alguna generación de código?

Puede realizar una clonación profunda de dos maneras: mediante la implementación de ICloneable (y llamando al método Object.MemberwiseClone), o mediante la serialización binaria.

Primera vía

La primera forma (y probablemente más rápida, pero no siempre la mejor) es implementar la interfaz ICloneable en cada tipo. La siguiente muestra ilustra. La clase C implementa ICloneable, y como esta clase hace referencia a otras clases D y E, los últimos también implementan esta interfaz. Dentro del método de clonación de C, llamamos el método de clonación de los otros tipos.

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

Ahora, cuando llama al método Clonar para una instancia de C, obtiene una clonación profunda de esa instancia:

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.

Nota: si las clases D y E tienen tipos de referencia, debe implementar su método de clonación como hicimos con la clase C. Y así sucesivamente.

Advertencias: 1-La muestra anterior es válida siempre que no haya una referencia circular. Por ejemplo, si la clase C tiene una auto-referencia (por ejemplo, un campo que es del tipo C), la implementación de la interfaz ICloneable no sería fácil, ya que el método de clonación en C puede ingresar a un bucle sin fin.

2-Otra cosa a tener en cuenta es que el método MemberwiseClone es un método protegido de la clase Object. Esto significa que puede usar este método solo dentro del código de la clase, como se muestra arriba. Esto significa que no puedes usarlo para clases externas.

Por lo tanto, la implementación de ICloneable solo es válida cuando las dos advertencias anteriores no existen. De lo contrario, debe utilizar la técnica de serialización binaria.

Segunda vía

La serialización binaria se puede utilizar para la clonación profunda sin los problemas enumerados anteriormente (especialmente la referencia circular). Aquí hay un método genérico que realiza una clonación profunda utilizando la serialización binaria:

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

Aquí se explica cómo usar este método:

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!
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top