2009-04-28 5 views
3

Nous avons une application qui effectue des comparaisons sur des objets de données pour déterminer si une version de l'objet est différente d'une autre. Notre application effectue également une mise en cache étendue de ces objets, et nous avons rencontré un léger problème de performances lors de ces comparaisons.Clonage efficace des objets mis en cache

est ici le flux de travail:

  1. élément de données 1 est l'élément en cours dans la mémoire. Cet élément a été initialement récupéré du cache et cloné en profondeur (tous les sous-objets tels que les dictionnaires, etc.). L'élément de données 1 est ensuite modifié et ses propriétés sont modifiées.
  2. Nous comparons ensuite cet objet avec la version d'origine stockée dans le cache. Étant donné que l'élément de données 1 a été cloné et que ses propriétés ont été modifiées, ces objets doivent être différents.

Il y a quelques problèmes ici.

Le problème principal est que notre méthode de clonage profond est très coûteuse. Nous l'avons profilé contre un clone peu profond et il était 10 fois plus lent. Ce n'est pas bon. Voici notre méthode pour clone profond:

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

Nous utilisions d'abord ce qui suit pour cloner:

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

C'était plus performant, mais parce qu'il fait un clone peu profond tous les objets complexes qui étaient propriétés cet objet, tel que Dictionnaires, etc., n'a pas été cloné. L'objet contiendrait toujours la même référence que l'objet qui se trouvait dans le cache, donc les propriétés seraient les mêmes lors de la comparaison.

Alors, est-ce que quelqu'un a un moyen efficace de faire un clone profond sur des objets C# qui couvrirait l'ensemble du clonage objet graphique?

+0

En supposant que vous voulez que la méthode Clone générique() parce que vous ne voulez pas mettre en œuvre ICloneable sur tout? –

+0

Ceci est un clonage juste un objet spécifique. Cet objet est l'objet de données principal de notre application. Est-ce que cela répond à votre question? –

Répondre

6

Tu ne vas pas être en mesure d'obtenir beaucoup mieux que votre sérialisation binaire générique sans mettre en œuvre Explicitement ICloneable sur tous vos objets de données qui doivent être cloné. Une autre voie possible est la réflexion, mais vous ne serez pas content non plus si vous recherchez des performances.

Je considérerais prendre le coup avec ICloneable pour la copie profonde et/ou IComparable pour comparer si les objets sont différents ... si les performances sont si grandes d'un problème pour vous.

1

Peut-être que vous ne devriez pas cloner en profondeur alors?

Autres options:

1) Faites votre objet « en cache » rappelez-vous son état d'origine et de faire il mise à jour « changé » drapeau tous les temps quoi que ce soit des changements. 2) Je ne me souviens pas de l'état d'origine et je signale simplement que l'objet est sale une fois que quelque chose a changé. Puis rechargez l'objet de la source d'origine pour comparer. Je parie que vos objets changent moins souvent que ne changent pas, et encore moins reviennent à la même valeur.

+1

DirtyFlag? C'est une merde de travail et vous perdriez des propriétés automatiques, le code jonché de SetIsDirty() et facile à oublier pour définir le drapeau (facile de créer des bogues). Il aurait beaucoup plus d'avantages à implémenter IComparable avec Tri, etc ... –

+0

Si vous n'utilisez pas PostSharp, vous n'avez rien à changer et vous bénéficiez de tous les avantages de PropertyChanged, et vous pouvez l'utiliser pour cela. beaucoup plus. – MBoros

1

Il est possible que ma réponse ne peut pas appliquer à votre cas parce que je ne sais pas ce que vos restrictions et exigences sont, mais mon sentiment serait qu'un clonage d'usage général peut être problématique. Comme vous l'avez déjà rencontré, la performance peut être un problème. Quelque chose doit identifier des instances uniques dans le graphe d'objet et ensuite créer une copie exacte.C'est ce que le sérialiseur binaire fait pour vous, mais il fait aussi plus (la sérialisation elle-même). Je ne suis pas surpris de voir que c'est plus lent que prévu. J'ai une expérience similaire (accessoirement aussi liée à la mise en cache). Mon approche serait d'implémenter le clonage par moi-même; c'est-à-dire implémentez IClonnable pour les classes qui ont réellement besoin d'être clonées. Combien y a-t-il de classes dans votre application que vous mettez en cache? S'il y en a trop (pour coder manuellement le clonage), serait-il sensé de considérer la génération de code?

0

Vous pouvez effectuer un clonage en profondeur de deux manières: en implémentant ICloneable (et en appelant la méthode Object.MemberwiseClone), ou en procédant à une sérialisation binaire.

Première voie

La première (et probablement plus rapide, mais pas toujours la meilleure) façon est de mettre en œuvre l'interface ICloneable dans chaque type. L'exemple ci-dessous illustre. La classe C implémente ICloneable, et comme cette classe référence d'autres classes D et E, ces derniers implémentent également cette interface. À l'intérieur de la méthode Clone de C, nous appelons la méthode Clone des autres types.

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 

Maintenant, lorsque vous appelez la méthode Clone pour une instance de C, vous obtenez un clonage profond de cette instance:

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. 

Note: Si les classes D et E ont des types de référence, vous doit implémenter leur méthode Clone comme nous l'avons fait pour la classe C. Et ainsi de suite.

Avertissements: 1-L'échantillon ci-dessus est valide tant qu'il n'y a pas de référence circulaire. Par exemple, si la classe C possède une référence automatique (par exemple, un champ de type C), l'implémentation de l'interface ICloneable ne sera pas facile, car la méthode Clone dans C peut entrer dans une boucle sans fin.

2-Une autre chose à noter est que la méthode MemberwiseClone est une méthode protégée de classe Object. Cela signifie que vous pouvez utiliser cette méthode uniquement à partir du code de la classe, comme indiqué ci-dessus. Cela signifie que vous ne pouvez pas l'utiliser pour des classes externes. Par conséquent, l'implémentation de ICloneable est valide uniquement lorsque les deux avertissements ci-dessus n'existent pas. Sinon, vous devez utiliser la technique de sérialisation binaire.

Second Way

Le sérialisation binaire peut être utilisé pour le clonage profond sans les problèmes mentionnés ci-dessus (en particulier la référence circulaire). Voici une méthode générique qui effectue-clonage en profondeur avec sérialisation binaire:

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 

Voici comment utiliser cette méthode:

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! 
Questions connexes