58 votes

Recherche de différences de propriétés entre deux objets C #

Le projet que je suis en train de travailler sur les besoins du simple enregistrement d'audit lorsqu'un utilisateur modifie son e-mail, adresse de facturation, etc. Les objets qui nous travaillons sont issues de différentes sources, l'une d'un service WCF, de l'autre un service web.

J'ai implémenté la méthode suivante à l'aide de la réflexion pour trouver les changements de propriétés sur deux objets différents. Cela génère une liste des propriétés qui ont des différences avec leurs anciennes et nouvelles valeurs.

public static IList GenerateAuditLogMessages(T originalObject, T changedObject)
{
    IList list = new List();
    string className = string.Concat("[", originalObject.GetType().Name, "] ");

    foreach (PropertyInfo property in originalObject.GetType().GetProperties())
    {
        Type comparable =
            property.PropertyType.GetInterface("System.IComparable");

        if (comparable != null)
        {
            string originalPropertyValue =
                property.GetValue(originalObject, null) as string;
            string newPropertyValue =
                property.GetValue(changedObject, null) as string;

            if (originalPropertyValue != newPropertyValue)
            {
                list.Add(string.Concat(className, property.Name,
                    " changed from '", originalPropertyValue,
                    "' to '", newPropertyValue, "'"));
            }
        }
    }

    return list;
}

Je suis à la recherche pour le Système.IComparable parce que "Tous les types numériques (tels que Int32 et Double) mettre en œuvre IComparable, comme String, Char, et DateTime." Cela semblait la meilleure façon de trouver un bien qui n'est pas une classe personnalisée.

Puisant dans l'événement PropertyChanged généré par la WCF ou de proxy de service web code sonnait bien, mais ne me donne pas assez d'info pour mon des journaux d'audit (anciennes et nouvelles valeurs).

À la recherche pour l'entrée que si il ya une meilleure façon de le faire, merci!

@Aaronaught, voici un exemple de code qui génère une correspondance positive basée sur le fait de faire de l'objet.Est égal à:

Address address1 = new Address();
address1.StateProvince = new StateProvince();

Address address2 = new Address();
address2.StateProvince = new StateProvince();

IList list = Utility.GenerateAuditLogMessages(address1, address2);

"[Adresse] StateProvince changé de 'MyAccountService.StateProvince' à 'MyAccountService.StateProvince'"

C'est à deux instances différentes de la StateProvince classe, mais les valeurs des propriétés sont les mêmes (tous les null dans ce cas). Nous ne sommes pas en substitution de la méthode equals.

28voto

Aaronaught Points 73049

IComparable est pour la commande des comparaisons. Utiliser IEquatable au lieu de cela, ou tout simplement utiliser la statique System.Object.Equals méthode. Ce dernier a l'avantage de travailler aussi si l'objet n'est pas un type primitif, mais encore définit son propre comparaison d'égalité en substituant Equals.

object originalValue = property.GetValue(originalObject, null);
object newValue = property.GetValue(changedObject, null);
if (!object.Equals(originalValue, newValue))
{
    string originalText = (originalValue != null) ?
        originalValue.ToString() : "[NULL]";
    string newText = (newText != null) ?
        newValue.ToString() : "[NULL]";
    // etc.
}

Ce n'est évidemment pas parfait, mais si vous êtes seulement de le faire avec des classes qui vous contrôle, vous pouvez vous assurer qu'il fonctionne toujours pour vos besoins particuliers.

Il existe d'autres méthodes pour comparer les objets (tels que des sommes de contrôle, la sérialisation, etc.) mais c'est probablement la plus fiable si les classes ne pas appliquer systématiquement IPropertyChanged et que vous voulez réellement connaître les différences.


Mise à jour pour le nouvel exemple de code:

Address address1 = new Address();
address1.StateProvince = new StateProvince();

Address address2 = new Address();
address2.StateProvince = new StateProvince();

IList list = Utility.GenerateAuditLogMessages(address1, address2);

La raison que l'utilisation d' object.Equals dans votre méthode de vérification des résultats dans un "hit" est parce que les instances ne sont effectivement pas d'égal!

Bien sûr, l' StateProvince peut être vide dans les deux cas, mais address1 et address2 ont encore des valeurs non nulles pour l' StateProvince de la propriété et chaque cas est différent. Par conséquent, address1 et address2 ont des propriétés différentes.

Nous allons inverser les autour de, prendre ce code par exemple:

Address address1 = new Address("35 Elm St");
address1.StateProvince = new StateProvince("TX");

Address address2 = new Address("35 Elm St");
address2.StateProvince = new StateProvince("AZ");

Devraient-ils être considérés comme égaux? Eh bien, ils seront, à l'aide de votre méthode, car StateProvince ne met pas en oeuvre IComparable. C'est la seule raison pourquoi votre méthode a signalé que les deux objets ont la même dans la boite d'origine. Depuis l' StateProvince classe n'implémente pas IComparable, le tracker juste ignore cette propriété entièrement. Mais ces deux adresses sont clairement pas d'égal!

C'est pourquoi j'ai d'abord proposé d'utiliser object.Equals, car alors vous pouvez le remplacer dans l' StateProvince méthode pour obtenir de meilleurs résultats:

public class StateProvince
{
    public string Code { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        StateProvince sp = obj as StateProvince;
        if (object.ReferenceEquals(sp, null))
            return false;

        return (sp.Code == Code);
    }

    public bool Equals(StateProvince sp)
    {
        if (object.ReferenceEquals(sp, null))
            return false;

        return (sp.Code == Code);
    }

    public override int GetHashCode()
    {
        return Code.GetHashCode();
    }

    public override string ToString()
    {
        return string.Format("Code: [{0}]", Code);
    }
}

Une fois que vous avez fait cela, l' object.Equals code fonctionne parfaitement. Au lieu de naïvement à vérifier si oui ou non address1 et address2 littéralement avoir le même StateProvince de référence, il va vérifier pour la sémantique de l'égalité.


L'autre façon de contourner cela est d'étendre le code de suivi à fait descendre dans les sous-objets. En d'autres termes, pour chaque propriété, vérifier l' Type.IsClass , et éventuellement l' Type.IsInterface de la propriété, et si true, puis, de manière récursive invoquer le changement de méthode de suivi sur la propriété elle-même, la préfixation de la vérification des résultats retournés de manière récursive avec le nom de la propriété. Donc, si vous voulez retrouver avec un changement pour StateProvinceCode.

J'utilise l'approche ci-dessus, parfois trop, mais il est plus facile de substituer Equals sur les objets pour lesquels vous souhaitez comparer sémantique de l'égalité (c'est à dire d'audit) et de fournir un ToString substituer à rendre clair ce qui a changé. Il n'a pas d'échelle de profondeur de nidification, mais je pense que c'est rare de vouloir vérification de cette façon.

La dernière astuce est de définir votre propre interface, dire IAuditable<T>, qui prend une deuxième instance du même type en paramètre et retourne en fait une liste (ou énumérable) de toutes les différences. Il est similaire à la notre substituée object.Equals méthode ci-dessus, mais donne plus d'informations. C'est utile lorsque l'objet graphique est vraiment compliqué et vous savez que vous ne pouvez pas compter sur la Réflexion ou l' Equals. Vous pouvez combiner cela avec l'approche ci-dessus; vraiment tout ce que vous avez à faire est de substituer IComparable votre IAuditable et invoquer l' Audit méthode si elle implémente cette interface.

20voto

bkaid Points 29335

Ce projet sur codeplex vérifie presque tous les types de propriétés et peut être personnalisé à votre guise.

11voto

Mike Two Points 16706

Vous voudrez peut-être consulter le testapi de Microsoft. Il possède une API de comparaison d'objets qui effectue des comparaisons approfondies. Ce serait peut-être exagéré pour vous, mais cela pourrait valoir la peine d'être examiné.

 var comparer = new ObjectComparer(new PublicPropertyObjectGraphFactory());
IEnumerable<ObjectComparisonMismatch> mismatches;
bool result = comparer.Compare(left, right, out mismatches);

foreach (var mismatch in mismatches)
{
    Console.Out.WriteLine("\t'{0}' = '{1}' and '{2}'='{3}' do not match. '{4}'",
        mismatch.LeftObjectNode.Name, mismatch.LeftObjectNode.ObjectValue,
        mismatch.RightObjectNode.Name, mismatch.RightObjectNode.ObjectValue,
        mismatch.MismatchType);
}
 

2voto

Dave Black Points 1396

Vous ne voulez jamais à mettre en œuvre GetHashCode sur mutable propriétés (propriétés qui pourrait être changé par quelqu'un) - c'est à dire non-privé, les organismes de normalisation.

Imaginez ce scénario:

  1. vous mettez une instance de l'objet dans une collection qui utilise GetHashCode() "sous les couvertures" ou directement (table de hachage).
  2. Puis quelqu'un modifie la valeur du champ ou de la propriété que vous avez utilisé dans votre GetHashCode() de la mise en œuvre.

Devinez quoi...votre objet est définitivement perdu dans la collection, car la collection utilise GetHashCode() pour la trouver! Vous avez effectivement changé le hashcode de la valeur de ce qui a été placée à l'origine dans la collection. Probablement pas ce que tu voulais.

0voto

Phil H Points 10133

Je pense que cette méthode est assez soignée, elle évite la répétition ou l'ajout de rien aux classes. Que cherchez-vous de plus?

La seule alternative serait de générer un dictionnaire d'état pour les anciens et les nouveaux objets et d'écrire une comparaison pour eux. Le code permettant de générer le dictionnaire d’état pourrait réutiliser toute la sérialisation dont vous disposez pour stocker ces données dans la base de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X