Comparaison des propriétés d’objet en c # [fermé]

111

C'est ce que j'ai proposé comme méthode sur une classe héritée par beaucoup de mes autres classes. L'idée est qu'elle permet la simple comparaison entre les propriétés des objets du même type.

Maintenant, cela fonctionne - mais dans l'intérêt d'améliorer la qualité de mon code, j'ai pensé que je le jetterais pour examen. Comment peut-il être meilleur / plus efficace / etc.?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}
clou
la source
3
Au fait, connaissez-vous ce site SE: codereview.stackexchange.com
wip
Il existe quelques bibliothèques de comparaison d'objets: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal
... et une tonne d'implémenteurs de comparateurs d'égalité génériques, dont certains sont: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . Ce dernier groupe pourrait avoir une portée et une flexibilité limitées quant à ce qu'il peut réaliser.
nawfal
Je vote pour fermer cette question comme hors-sujet car elle appartient à la révision du code
Xiaoy312

Réponses:

160

Je cherchais un extrait de code qui ferait quelque chose de similaire pour aider à écrire un test unitaire. Voici ce que j'ai fini par utiliser.

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

ÉDITER:

Même code que ci-dessus mais utilise les méthodes LINQ et Extension:

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }
Taras Alenin
la source
Big T - tout à fait un vieux, mais sert certainement un excellent objectif à la fois pour les tests et les comparaisons simples .. merci +1
jim tollan
1
C'est bien, mais j'ai trouvé que cela ne fonctionnait pas avec des objets plus complexes. Par exemple, j'ai un objet avec des chaînes (il les compare très bien) mais cet objet a également une liste d'un autre objet, qu'il ne compare pas correctement, il faut donc le répéter d'une manière ou d'une autre.
Ryan Thomas
1
J'ai dû ajouter aux critères dans le premier où deux autres critères car il faut exclure les propriétés indexées qui jettent une exception dans les autres cas. Voici les critères de cette erreur: pi.GetIndexParameters (). Length == 0. Et le deuxième critère pour résoudre le problème énoncé par @RyanThomas est le suivant: pi.GetUnderlyingType (). IsSimpleType (). Comme vous le verrez, IsSimpleType est une extension qui n'existe pas pour la classe Type. J'ai modifié la réponse pour ajouter toutes ces conditions et l'extension.
Samuel
64

MISE À JOUR: La dernière version de Compare-Net-Objects se trouve sur GitHub , contient le package NuGet et le didacticiel . Cela peut être appelé comme

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

Ou si vous avez besoin de modifier une configuration, utilisez

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

La liste complète des paramètres configurables se trouve dans ComparisonConfig.cs

Réponse originale:

Les limitations que je vois dans votre code:

  • Le plus important est qu'il ne fait pas de comparaison d'objets approfondie.

  • Il ne fait pas de comparaison élément par élément dans le cas où les propriétés sont des listes ou contiennent des listes en tant qu'éléments (cela peut aller à n niveaux).

  • Il ne tient pas compte du fait que certains types de propriétés ne doivent pas être comparés (par exemple une propriété Func utilisée à des fins de filtrage, comme celle de la classe PagedCollectionView).

  • Il ne garde pas trace des propriétés réellement différentes (vous pouvez donc les montrer dans vos assertions).

Je cherchais aujourd'hui une solution à des fins de test unitaire pour faire une comparaison approfondie propriété par propriété et j'ai fini par utiliser: http://comparenetobjects.codeplex.com .

C'est une bibliothèque gratuite avec une seule classe que vous pouvez simplement utiliser comme ceci:

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

En outre, il peut être facilement recompilé pour Silverlight. Copiez simplement la classe dans un projet Silverlight et supprimez une ou deux lignes de code pour les comparaisons qui ne sont pas disponibles dans Silverlight, comme la comparaison des membres privés.

Liviu Trifoi
la source
2
Liviu, j'ai remarqué votre commentaire sur la classe non compatible avec Silverlight. Je viens de le changer pour qu'il soit compatible avec Silverlight et Windows Phone 7. Faites une mise à jour. Voir l'ensemble de modifications 74131 sur comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer
Cela semble prometteur. Je vais l'essayer
DJ Burb
Merci pour le bel exemple! En outre, le IgnoreObjectTypesréglage peut être utile lorsqu'il existe différents types.
Sergey Brunov
La version 2.0 a une version de bibliothèque de classes portable compatible avec Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS et Xamarin Droid
Greg Finzer
DifferencesStringa été abandonné dans la classe CompareObjects. Mais maintenant, vous pouvez obtenir cela à partir du résultat de comparaison à la place:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze
6

Je pense qu'il serait préférable de suivre le modèle pour Override Object # Equals ()
Pour une meilleure description: Lisez le C # effectif de Bill Wagner - Point 9 Je pense

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • Également dans les méthodes qui vérifient l'égalité, vous devez renvoyer true ou false. soit ils sont égaux, soit ils ne le sont pas .. au lieu de lancer une exception, retournez false.
  • J'envisagerais de remplacer Object # Equals.
  • Même si vous devez avoir pris en compte cela, l'utilisation de Reflection pour comparer les propriétés est supposée lente (je n'ai pas de chiffres pour le confirmer). Il s'agit du comportement par défaut pour valueType # Equals en C # et il est recommandé de remplacer Equals pour les types valeur et d'effectuer une comparaison par membre pour les performances. (Auparavant, je lis rapidement ceci car vous avez une collection d'objets Property personnalisés ... c'est dommage.)

Mise à jour-décembre 2011:

  • Bien sûr, si le type a déjà une production Equals (), vous avez besoin d'une autre approche.
  • Si vous utilisez ceci pour comparer des structures de données immuables exclusivement à des fins de test, vous ne devriez pas ajouter un Equals aux classes de production (quelqu'un pourrait durcir les tests en chaînant l'implémentation Equals ou vous pouvez empêcher la création d'une implémentation Equals requise en production) .
Gishu
la source
J'ai rencontré des problèmes avec la surcharge de .Equals () parce que j'essaie de l'implémenter sur une classe de base qui est héritée ... parce que je ne connais pas les clés de la classe contre laquelle elle sera exécutée, je ne peux pas implémenter un remplacement décent pour GetHasCode () (requis lorsque vous remplacez Equals ()).
nailitdown le
La condition est que si objA.Equals (objB) alors objA.GetHashCode () == objB.GetHashCode (). GetHashCode ne devrait pas dépendre de l'état / des données mutables d'une classe ... Je n'ai pas compris ce que vous vouliez dire par clés pour la classe .. On dirait que quelque chose peut être résolu. Le type de base n'a-t-il pas les «clés»?
Gishu
6

Si les performances n'ont pas d'importance, vous pouvez les sérialiser et comparer les résultats:

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();
Edward Brey
la source
4
essayé il y a quelques instants, vous vous demanderiez combien d'objets ne sont pas sérialisables ...
Offler
5

Je pense que la réponse de Big T était assez bonne, mais la comparaison approfondie manquait, alors je l'ai légèrement modifiée:

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}
Greg
la source
4

J'ajouterais la ligne suivante à la méthode PublicInstancePropertiesEqual pour éviter les erreurs de copier-coller:

Assert.AreNotSame(self, to);
thanei
la source
2

Remplacez-vous .ToString () sur tous vos objets qui se trouvent dans les propriétés? Sinon, cette deuxième comparaison pourrait revenir avec null.

De plus, dans cette deuxième comparaison, je suis sur la clôture de la construction de! (A == B) par rapport à (A! = B), en termes de lisibilité dans six mois / deux ans. La ligne elle-même est assez large, ce qui est normal si vous avez un écran large, mais peut ne pas s'imprimer très bien. (pinailler)

Tous vos objets utilisent-ils toujours des propriétés telles que ce code fonctionnera? Pourrait-il y avoir des données internes non propriétaires qui pourraient être différentes d'un objet à l'autre, mais toutes les données exposées sont les mêmes? Je pense à certaines données qui pourraient changer avec le temps, comme deux générateurs de nombres aléatoires qui atteignent le même nombre à un moment donné, mais qui vont produire deux séquences d'informations différentes, ou n'importe quelles données qui ne sont pas exposées via l'interface de propriété.

mmr
la source
bons points -! = ... d'accord, point pris. ToString () était une tentative de contournement .GetValue renvoyant un objet (donc la comparaison toujours fausse, car c'est une comparaison de référence) .. y a-t-il un meilleur moyen?
nailitdown le
Si GetValue renvoie un objet, pouvez-vous à nouveau utiliser cette fonction? c'est à dire, appelez PropertiesEqual sur les objets retournés?
mmr
1

Si vous ne comparez que des objets du même type ou plus bas dans la chaîne d'héritage, pourquoi ne pas spécifier le paramètre comme type de base plutôt que comme objet?

Faites également des vérifications nulles sur le paramètre.

De plus, j'utiliserais 'var' juste pour rendre le code plus lisible (si son code c # 3)

De plus, si l'objet a des types de référence comme propriétés, vous appelez simplement ToString () sur eux, ce qui ne compare pas vraiment les valeurs. Si ToString n'est pas écrasé, il retournera simplement le nom du type sous forme de chaîne qui pourrait renvoyer des faux positifs.

DarkwingDuck
la source
bon point sur les types de référence - dans mon cas, cela n'a pas d'importance, mais il y a de fortes chances que ce soit le cas.
nailitdown
1

La première chose que je suggérerais serait de diviser la comparaison réelle afin qu'elle soit un peu plus lisible (j'ai également retiré le ToString () - est-ce nécessaire?):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

La prochaine suggestion serait de minimiser l'utilisation de la réflexion autant que possible - c'est vraiment lent. Je veux dire, vraiment lent. Si vous envisagez de faire cela, je vous suggère de mettre en cache les références de propriété. Je ne suis pas intimement familier avec l'API Reflection, donc si ce n'est pas le cas, ajustez-le pour le faire compiler:

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

Cependant, je dois dire que je suis d'accord avec les autres affiches. Cela sent paresseux et inefficace. Vous devriez plutôt implémenter IComparable :-).

Tsimon
la source
Je regardais juste IComparable mais il semblait que c'était pour trier et ordonner .. est-ce vraiment utile pour comparer l'égalité de deux objets?
nailitdown le
Absolument, car .Equals (objet o) est défini comme this.CompareTo (o) == 0. Donc, equals utilise ComparesTo () pour déterminer l'égalité. Ce sera beaucoup plus efficace (et pratique courante) que d'utiliser la réflexion.
tsimon
Je peux me tromper en supposant que Equals est implémenté (ou devrait être implémenté) en référence à CompareTo (). Vous devriez envisager de remplacer Equals comme décrit ici: stackoverflow.com/questions/104158/…
tsimon
1

ici est révisé un pour traiter null = null comme égal

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }
Hossein
la source
Et si j'avais un graphe d'objets profond, quelle est la meilleure façon d'utiliser ci-dessus pour renvoyer une liste des anciennes et nouvelles propriétés qui ont été modifiées?
Rod
1

J'ai fini par faire ceci:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

Usage:

    if (Compare<ObjectType>(a, b))

Mettre à jour

Si vous souhaitez ignorer certaines propriétés par leur nom:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

Usage:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))
BjarkeCK
la source
1

Vous pouvez optimiser votre code en appelant GetProperties une seule fois par type:

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}
Moti Elbilya
la source
1

Pour être complet, je veux ajouter une référence à http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Il a une logique plus complète que la plupart des autres réponses sur cette page.

Cependant , je préfère COMPARE-Net-bibliothèque d' objets https://github.com/GregFinzer/Compare-Net-Objects (ci -après par Liviu Trifoi de réponse )
La bibliothèque a package NuGet http://www.nuget.org/packages/ CompareNETObjects et plusieurs options à configurer.

Michael Freidgeim
la source
1

Assurez-vous que les objets ne le sont pas null.

Avoir obj1et obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );
Ric Tokyo
la source
et s'ils sont tous les deux nuls? ne sont-ils pas alors égaux?
mmr
bon point sur les valeurs nulles, dans mon cas, l'utilisation de .Equals () ne semble pas fonctionner, c'est pourquoi j'ai trouvé cette solution
nailitdown
eh bien, le cas que je teste est deux objets, un nouvellement créé, un de la session. la comparaison des deux avec .Equals () renvoie false même si les deux ont des valeurs de propriété identiques
nailitdown
0

Cela fonctionne même si les objets sont différents. vous pouvez personnaliser les méthodes dans la classe des utilitaires, peut-être souhaitez-vous également comparer les propriétés privées ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}
Tono Nam
la source
Ce code n'est pas efficace à 100%. il ne fonctionne pas dans certaines situations par exemple s'il contient une propriété de type object.
Tono Nam
0

Mise à jour sur la réponse de Liviu ci-dessus - CompareObjects.DifferencesString est obsolète.

Cela fonctionne bien dans un test unitaire:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);
Daniel de Zwaan
la source
1
C'est génial que vous ayez corrigé la dépréciation, mais je pense que cette réponse devrait en fait être un commentaire dans la réponse de Liviu. Surtout parce que votre exemple de code (comparé à celui de Liviu) ne dispose pas des paramètres de CompareLogic (qui, j'en suis sûr, sont importants), ainsi que du message d'assert (qui était obsolète). L'affirmation peut être corrigée avec:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze
0

Cette méthode obtiendra propertiesde la classe et comparera les valeurs pour chacun property. Si l'une des valeurs est différente, elle le sera return false, sinon elle le sera return true.

public static bool Compare<T>(T Object1, T object2)
{
    //Get the type of the object
    Type type = typeof(T);

    //return false if any of the object is false
    if (Object1 == null || object2 == null)
        return false;

    //Loop through each properties inside class and get values for the property from both the objects and compare
    foreach (System.Reflection.PropertyInfo property in type.GetProperties())
    {
        if (property.Name != "ExtensionData")
        {
            string Object1Value = string.Empty;
            string Object2Value = string.Empty;
            if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
            if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
            if (Object1Value.Trim() != Object2Value.Trim())
            {
                return false;
            }
        }
    }
    return true;
}

Usage:

bool isEqual = Compare<Employee>(Object1, Object2)

Expéditeur
la source
0

Pour développer la réponse de @nawfal: s, je l'utilise pour tester des objets de différents types dans mes tests unitaires afin de comparer des noms de propriétés égaux. Dans mon cas, entité de base de données et DTO.

Utilisé comme ça dans mon test;

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}
Sgedda
la source
0

Parfois, vous ne voulez pas comparer toutes les propriétés publiques et ne voulez comparer que le sous-ensemble d'entre elles, donc dans ce cas, vous pouvez simplement déplacer la logique pour comparer la liste de propriétés souhaitée à la classe abstraite

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

et utilisez cette classe abstraite plus tard pour comparer les objets

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}
M. Citrouille
la source
0

ma solution est inspirée de la réponse d'Aras Alenin ci-dessus où j'ai ajouté un niveau de comparaison d'objets et un objet personnalisé pour les résultats de comparaison. Je suis également intéressé à obtenir le nom de la propriété avec le nom de l'objet:

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

Utilisation de la classe suivante pour stocker les résultats de comparaison

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

Et un exemple de test unitaire:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
EricBDev
la source