2009-04-28 7 views
3

Wir haben eine Anwendung, die Vergleiche an Datenobjekten durchführt, um festzustellen, ob sich eine Version des Objekts von einer anderen unterscheidet. Unsere Anwendung führt auch ein umfangreiches Caching dieser Objekte durch, und wir haben bei diesen Vergleichen ein kleines Leistungsproblem bekommen.Effizientes Klonen zwischengespeicherter Objekte

Hier ist der Workflow:

  1. Datenpunkt 1 ist das aktuelle Element im Speicher. Dieser Eintrag wurde ursprünglich aus dem Cache abgerufen und tief geklont (alle Unterobjekte wie Wörterbücher usw.). Datenelement 1 wird dann bearbeitet und seine Eigenschaften werden geändert.
  2. Wir vergleichen dann dieses Objekt mit der ursprünglichen Version, die im Cache gespeichert wurde. Da Datenelement 1 geklont wurde und seine Eigenschaften geändert wurden, sollten diese Objekte unterschiedlich sein.

Es gibt ein paar Probleme hier.

Das Hauptproblem ist unsere tiefe Klonmethode ist sehr teuer. Wir haben es gegen einen flachen Klon profiliert und es war 10x langsamer. Das ist Mist. Hier ist unsere Methode, um tiefe Klon:

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

Wir wurden zunächst mit dem folgenden klonen:

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

Das war mehr performant, sondern weil es tut ein flacher Klon alle komplexen Objekte, die Eigenschaften waren Dieses Objekt, z. B. Wörterbücher usw., wurde nicht geklont. Das Objekt würde immer noch den gleichen Verweis wie das Objekt enthalten, das sich im Cache befand, daher wären die Eigenschaften beim Vergleich gleich.

Hat also jemand eine effiziente Möglichkeit, einen tiefen Klon auf C# -Objekte zu erstellen, der das Klonen des gesamten Objektgraphen abdecken würde?

+0

Angenommen, Sie möchten, dass generische Clone() -Methode, weil Sie nicht wollen, zu implementieren, Auf alles klonbar? –

+0

Dies ist Klonen nur ein bestimmtes Objekt. Dieses Objekt ist das Kerndatenobjekt in unserer App. Beantwortet das deine Frage? –

Antwort

6

Sie werden nicht viel besser als Ihre generische binäre Serialisierung erhalten, ohne ICloneable explizit auf alle Ihre Datenobjekte zu implementieren, die geklont werden müssen. Eine andere mögliche Route ist die Reflektion, aber Sie werden auch dann nicht damit zufrieden sein, wenn Sie nach Leistung suchen.

Ich würde in Betracht ziehen, den Hit mit ICloneable für tiefe Kopie und/oder IComparable für den Vergleich zu nehmen, wenn die Objekte unterschiedlich sind ... wenn die Leistung ein großes Problem für Sie ist.

1

Vielleicht sollten Sie dann nicht tief klonen?

Weitere Optionen:

1) Stellen Sie Ihre „Im Cache“ Objekt seinen ursprünglichen Zustand zu erinnern und machen es Update „geändert“ Flagge jedes Mal etwas ändert.

2) Erinnere dich nicht an den Originalzustand und markiere das Objekt nur dann als schmutzig, wenn sich jemals etwas geändert hat. Laden Sie dann das Objekt von der Originalquelle zum Vergleichen neu. Ich wette, deine Objekte ändern sich weniger häufig, als sie sich nicht ändern, und noch seltener wechseln sie auf den gleichen Wert zurück.

+1

DirtyFlag? Das ist ein Haufen Arbeit und du verlierst die automatischen Eigenschaften, den Code, der mit SetIsDirty() übersät ist, und vergisst leicht, die Flagge zu setzen (einfach Fehler zu erzeugen). Er würde viel mehr Nutzen aus der Implementierung von IComparable mit Sortierung, etc ... –

+0

Wenn Sie PostSharp nicht verwenden, dann müssen Sie nichts ändern, und Sie erhalten alle Vorteile von PropertyChanged, und Sie können es für so verwenden viel mehr. – MBoros

1

Es ist möglich, dass meine Antwort möglicherweise nicht auf Ihren Fall zutrifft, weil ich nicht weiß, was Ihre Einschränkungen und Anforderungen sind, aber mein Gefühl wäre, dass ein Mehrzweck-Klonen problematisch sein könnte. Wie Sie bereits festgestellt haben, kann die Leistung ein Problem sein. Etwas muss eindeutige Instanzen im Objektdiagramm identifizieren und dann eine exakte Kopie erstellen.Dies ist, was der binäre Serializer für Sie tut, aber es tut auch mehr (die Serialisierung selbst). Ich bin nicht überrascht, dass es langsamer ist als erwartet. Ich habe ähnliche Erfahrung (übrigens auch im Zusammenhang mit Caching). Mein Ansatz wäre, das Klonen selbst zu implementieren; d.h. Implementieren von IClonnable für Klassen, die tatsächlich geklont werden müssen. Wie viele Klassen gibt es in Ihrer Anwendung, die Sie zwischenspeichern? Wenn es zu viele gibt (um das Klonen manuell zu codieren), wäre es sinnvoll, eine Code-Generierung in Erwägung zu ziehen?

0

können Sie tief Klonen auf zwei Arten machen: Durch ICloneable Umsetzung (und die Object.MemberwiseClone Aufruf der Methode) oder durch binäre Serialisierung.

Erste Way

Die erste (und wahrscheinlich schneller, aber nicht immer die beste) Art und Weise ist es, die ICloneable Schnittstelle in jeder Art zu implementieren. Das folgende Beispiel veranschaulicht. Klasse C implementiert ICloneable, und da diese Klasse andere Klassen D und E referenziert, implementieren die letzteren auch diese Schnittstelle. Innerhalb der Clone-Methode von C rufen wir die Clone-Methode der anderen Typen auf.

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 

Nun, wenn Sie die Clone-Methode für eine Instanz von C nennen, erhalten Sie einen tief Klonen dieser Instanz:

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. 

Hinweis: Wenn Klassen D und E Referenz-Typen haben, können Sie müssen ihre Clone-Methode implementieren, wie wir es für Klasse C getan haben. Und so weiter.

Warnungen: -1 Die Probe oben gültig ist, solange es keinen Kreis Referenz. Wenn beispielsweise Klasse C eine Selbstreferenz hat (z. B. ein Feld vom Typ C), wäre die Implementierung der ICloneable-Schnittstelle nicht einfach, da die Clone-Methode in C in eine Endlosschleife eintreten kann.

2-Eine andere Sache zu beachten ist, dass die MemberwiseClone-Methode eine geschützte Methode der Klasse Object ist. Dies bedeutet, dass Sie diese Methode nur innerhalb des Codes der Klasse verwenden können, wie oben gezeigt. Dies bedeutet, dass Sie es nicht für externe Klassen verwenden können.

Daher ist ICloneable Umsetzung nur dann gültig, wenn die beiden Warnungen oben gibt es nicht. Andernfalls sollten Sie die binäre Serialisierungstechnik verwenden.

Second Way

Die binäre Serialisierung kann oben aufgeführt, ohne die Probleme für tiefe Klonen verwendet werden (vor allem der Kreis Referenz). Hier ist eine generische Methode, die tief-Klonen unter Verwendung von binären Serialisierung durchführt:

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 

Hier ist, wie diese Methode zu verwenden:

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!