Meilleur moyen de comparer deux objets complexes

112

J'ai deux objets complexes comme Object1et Object2. Ils ont environ 5 niveaux d'objets enfants.

J'ai besoin de la méthode la plus rapide pour dire s'ils sont identiques ou non.

Comment cela pourrait-il être fait en C # 4.0?

Développeur
la source

Réponses:

101

Implémentez IEquatable<T>(généralement en même temps que le remplacement des méthodes Object.Equalset héritées Object.GetHashCode) sur tous vos types personnalisés. Dans le cas des types composites, invoquez la Equalsméthode des types contenus dans les types conteneurs . Pour les collections contenues, utilisez la SequenceEqualméthode d'extension, qui appelle en interne IEquatable<T>.Equalsou Object.Equalssur chaque élément. Cette approche vous obligera évidemment à étendre les définitions de vos types, mais ses résultats sont plus rapides que toutes les solutions génériques impliquant la sérialisation.

Edit : Voici un exemple artificiel avec trois niveaux d'imbrication.

Pour les types valeur, vous pouvez généralement simplement appeler leur Equalsméthode. Même si les champs ou les propriétés n'étaient jamais explicitement affectés, ils auraient toujours une valeur par défaut.

Pour les types de référence, vous devez d'abord appeler ReferenceEquals, qui vérifie l'égalité des références - cela servirait d'amélioration de l'efficacité lorsque vous référencez le même objet. Il traiterait également les cas où les deux références sont nulles. Si cette vérification échoue, confirmez que le champ ou la propriété de votre instance n'est pas nul (à éviter NullReferenceException) et appelez sa Equalsméthode. Puisque nos membres sont correctement typés, la IEquatable<T>.Equalsméthode est appelée directement, en contournant la Object.Equalsméthode surchargée (dont l'exécution serait légèrement plus lente en raison du type cast).

Lorsque vous remplacez Object.Equals, vous êtes également censé remplacer Object.GetHashCode; Je ne l'ai pas fait ci-dessous par souci de concision.

public class Person : IEquatable<Person>
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public Address Address { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Person);
    }

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

        return this.Age.Equals(other.Age) &&
            (
                object.ReferenceEquals(this.FirstName, other.FirstName) ||
                this.FirstName != null &&
                this.FirstName.Equals(other.FirstName)
            ) &&
            (
                object.ReferenceEquals(this.Address, other.Address) ||
                this.Address != null &&
                this.Address.Equals(other.Address)
            );
    }
}

public class Address : IEquatable<Address>
{
    public int HouseNo { get; set; }
    public string Street { get; set; }
    public City City { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Address);
    }

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

        return this.HouseNo.Equals(other.HouseNo) &&
            (
                object.ReferenceEquals(this.Street, other.Street) ||
                this.Street != null &&
                this.Street.Equals(other.Street)
            ) &&
            (
                object.ReferenceEquals(this.City, other.City) ||
                this.City != null &&
                this.City.Equals(other.City)
            );
    }
}

public class City : IEquatable<City>
{
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as City);
    }

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

        return
            object.ReferenceEquals(this.Name, other.Name) ||
            this.Name != null &&
            this.Name.Equals(other.Name);
    }
}

Mise à jour : Cette réponse a été écrite il y a plusieurs années. Depuis lors, j'ai commencé à m'éloigner de l'implémentation IEquality<T>de types mutables pour de tels scénarios. Il existe deux notions d'égalité: l' identité et l' équivalence . Au niveau de la représentation de la mémoire, ils sont généralement distingués comme «égalité de référence» et «égalité de valeur» (voir Comparaisons d'égalité ). Cependant, la même distinction peut également s'appliquer au niveau du domaine. Supposons que votre Personclasse possède une PersonIdpropriété, unique par personne distincte du monde réel. Est-ce que deux objets avec les mêmes valeurs PersonIdmais différentes Agedoivent être considérés comme égaux ou différents? La réponse ci-dessus suppose que l'on est après l'équivalence. Cependant, il existe de nombreux usages duIEquality<T>interface, comme les collections, qui supposent que de telles implémentations fournissent une identité . Par exemple, si vous remplissez a HashSet<T>, vous vous attendez généralement à ce qu'un TryGetValue(T,T)appel renvoie des éléments existants qui partagent simplement l'identité de votre argument, pas nécessairement des éléments équivalents dont le contenu est complètement le même. Cette notion est renforcée par les notes sur GetHashCode:

En général, pour les types de référence mutables, vous ne devez remplacer GetHashCode()que si:

  • Vous pouvez calculer le code de hachage à partir de champs qui ne sont pas modifiables; ou
  • Vous pouvez vous assurer que le code de hachage d'un objet mutable ne change pas tant que l'objet est contenu dans une collection qui repose sur son code de hachage.
Douglas
la source
J'obtiens cet objet via RIA Services ... Puis-je utiliser IEquatable <Foo> pour ces objets et l'obtenir sous le client WPF?
Développeur
1
Vous voulez dire que les classes sont générées automatiquement? Je n'ai pas utilisé les services RIA, mais je suppose que toutes les classes générées seraient déclarées comme partial- auquel cas, oui, vous pouvez implémenter leur Equalsméthode via une déclaration de classe partielle ajoutée manuellement qui fait référence aux champs / propriétés de la génération automatique une.
Douglas
Et si "Adresse adresse" était en fait "Adresse [] adresses", comment serait-elle implémentée?
guiomie du
2
Vous pouvez appeler la LINQ Enumerable.SequenceEqualméthode sur les tableaux: this.Addresses.SequenceEqual(other.Addresses). Cela appellerait en interne votre Address.Equalsméthode pour chaque paire d'adresses correspondantes, étant donné que la Addressclasse implémente l' IEquatable<Address>interface.
Douglas
2
Une autre catégorie de comparaison qu'un développeur pourrait vérifier est "WorksLike". Pour moi, cela signifie que même si deux instances peuvent avoir des valeurs de propriété inégales, le programme produira le même résultat en traitant les deux instances.
John Kurtz
95

Sérialiser les deux objets et comparer les chaînes résultantes

JoelFan
la source
1
Je ne vois pas pourquoi il y en aurait. La sérialisation est normalement un processus optimisé et vous devez dans tous les cas accéder à la valeur de chaque propriété.
JoelFan
5
Il y a un coût énorme. Vous générez un flux de données, ajoutez des chaînes, puis testez l'égalité des chaînes. Des ordres de grandeur, juste là-dedans. Sans oublier que la sérialisation utilisera la réflexion par défaut.
Jerome Haltom
2
Le flux de données n'est pas un gros problème, je ne vois pas pourquoi vous auriez besoin d'ajouter des chaînes ... tester l'égalité des chaînes est l'une des opérations les plus optimisées sur le marché ... vous avez peut-être un point avec la réflexion ... mais sur toute la sérialisation ne sera pas "des ordres de grandeur" pires que les autres méthodes. Vous devriez faire des tests de performances si vous soupçonnez des problèmes de performances ... Je n'ai pas rencontré de problèmes de performances avec cette méthode
JoelFan
12
Je le suis +1simplement parce que je n'ai jamais pensé à faire une comparaison d'égalité basée sur des valeurs de cette façon. C'est sympa et simple. Ce serait bien de voir quelques points de repère avec ce code.
Thomas
1
Ce n'est pas une bonne solution car les deux sérialisations peuvent mal tourner de la même manière. Par exemple, certaines propriétés de l'objet source peuvent ne pas avoir été sérialisées et, une fois désérialisées, seront définies sur null dans l'objet de destination. Dans un tel cas, votre test qui compare des chaînes passera, mais les deux objets ne sont en fait pas les mêmes!
stackMeUp
35

Vous pouvez utiliser la méthode d'extension, la récursivité pour résoudre ce problème:

public static bool DeepCompare(this object obj, object another)
{     
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  //Compare two object's class, return false if they are difference
  if (obj.GetType() != another.GetType()) return false;

  var result = true;
  //Get all properties of obj
  //And compare each other
  foreach (var property in obj.GetType().GetProperties())
  {
      var objValue = property.GetValue(obj);
      var anotherValue = property.GetValue(another);
      if (!objValue.Equals(anotherValue)) result = false;
  }

  return result;
 }

public static bool CompareEx(this object obj, object another)
{
 if (ReferenceEquals(obj, another)) return true;
 if ((obj == null) || (another == null)) return false;
 if (obj.GetType() != another.GetType()) return false;

 //properties: int, double, DateTime, etc, not class
 if (!obj.GetType().IsClass) return obj.Equals(another);

 var result = true;
 foreach (var property in obj.GetType().GetProperties())
 {
    var objValue = property.GetValue(obj);
    var anotherValue = property.GetValue(another);
    //Recursion
    if (!objValue.DeepCompare(anotherValue))   result = false;
 }
 return result;
}

ou comparer en utilisant Json (si l'objet est très complexe) Vous pouvez utiliser Newtonsoft.Json:

public static bool JsonCompare(this object obj, object another)
{
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  if (obj.GetType() != another.GetType()) return false;

  var objJson = JsonConvert.SerializeObject(obj);
  var anotherJson = JsonConvert.SerializeObject(another);

  return objJson == anotherJson;
}
Jonathan
la source
1
La première solution est géniale! J'aime que vous n'ayez pas à sérialiser json ou à implémenter d'ajouter du code aux objets eux-mêmes. Convient lorsque vous comparez simplement pour des tests unitaires. Puis-je suggérer d'ajouter une simple comparaison dans le cas où objValue et anotherValue sont tous deux égaux à null? Cela évitera qu'une exception NullReferenceException ne soit lancée lorsque vous essayez de faire null.Equals () // ReSharper désactiver une fois RedundantJumpStatement if (objValue == anotherValue) continue; // Garde de référence nulle else if (! objValue.Equals (anotherValue)) Fail (attendu, réel);
Mark Conway
3
Y a-t-il une raison d'utiliser DeepCompareau lieu d'appeler simplement CompareExrécursivement?
apostolov
3
Cela peut comparer la structure entière inutilement. Le remplacer resultpar return falsele rendrait plus efficace.
Tim Sylvester
24

Si vous ne souhaitez pas implémenter IEquatable, vous pouvez toujours utiliser Reflection pour comparer toutes les propriétés: - si elles sont de type valeur, comparez-les simplement - si elles sont de type référence, appelez la fonction de manière récursive pour comparer ses propriétés "internes" .

Je ne pense pas à la performance, mais à la simplicité. Cela dépend cependant de la conception exacte de vos objets. Cela peut devenir compliqué en fonction de la forme de vos objets (par exemple s'il existe des dépendances cycliques entre les propriétés). Il existe cependant plusieurs solutions que vous pouvez utiliser, comme celle-ci:

Une autre option consiste à sérialiser l'objet sous forme de texte, par exemple à l'aide de JSON.NET, et à comparer le résultat de la sérialisation. (JSON.NET peut gérer les dépendances cycliques entre les propriétés).

Je ne sais pas si par plus rapide vous entendez le moyen le plus rapide de l'implémenter ou un code qui s'exécute rapidement. Vous ne devez pas optimiser avant de savoir si vous en avez besoin. L'optimisation prématurée est la racine de tout Mal

JotaBe
la source
1
Je ne pense guère qu'une IEquatable<T>implémentation puisse être qualifiée de cas d'optimisation prématurée. La réflexion sera considérablement plus lente. L'implémentation par défaut de Equalspour les types de valeur personnalisés utilise la réflexion; Microsoft lui-même recommande de le remplacer pour les performances: «Remplacez la Equalsméthode pour un type particulier pour améliorer les performances de la méthode et représenter plus fidèlement le concept d'égalité pour le type.»
Douglas
1
Cela dépend du nombre de fois où il exécutera la méthode d'égalité: 1, 10, 100, 100, un million? Cela fera une grande différence. S'il peut utiliser une solution générique sans rien mettre en œuvre, il épargnera un temps précieux. Si c'est trop lent, alors il est temps d'implémenter IEquatable (et peut-être même d'essayer de créer un GetHashCode cachable ou intelligent) En ce qui concerne la vitesse de réflexion, je dois convenir qu'elle est plus lente ... ou beaucoup plus lente, selon la façon dont vous le faites ( c'est-à-dire réutiliser les types PropertyInfos et ainsi de suite, ou non).
JotaBe
@ Worthy7 Oui. Veuillez consulter le contenu du projet. Les tests sont un bon moyen de documenter par l'exemple. Mais, mieux que cela, si vous le recherchez, vous trouverez un fichier d'aide .chm. Donc, ce projet a une bien meilleure documentation que la plupart des projets.
JotaBe
Désolé vous avez raison, j'ai totalement manqué l'onglet "wiki". J'ai l'habitude que tout le monde écrive des choses dans le readme.
Worthy7 le
9

Sérialisez les deux objets et comparez les chaînes résultantes par @JoelFan

Donc, pour ce faire, créez une classe statique comme celle-ci et utilisez les extensions pour étendre TOUS les objets (afin que vous puissiez passer n'importe quel type d'objet, de collection, etc. dans la méthode)

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

public static class MySerializer
{
    public static string Serialize(this object obj)
    {
        var serializer = new DataContractJsonSerializer(obj.GetType());
        using (var ms = new MemoryStream())
        {
            serializer.WriteObject(ms, obj);
            return Encoding.Default.GetString(ms.ToArray());
        }
    }
}

Une fois que vous avez référencé cette classe statique dans un autre fichier, vous pouvez le faire:

Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();

Vous pouvez maintenant simplement utiliser .Equals pour les comparer. J'utilise ceci pour vérifier si les objets sont également dans des collections. Cela fonctionne vraiment bien.

ozzy432836
la source
Que faire si le contenu des objets est un tableau de nombres à virgule flottante. Il est très inefficace de les convertir en chaînes et la conversion est sujette aux transformations définies dans CultrureInfo. Cela ne fonctionnerait que si les données internes sont principalement des chaînes et des entiers. Sinon, ce serait un désastre.
John Alexiou
3
Et si un nouveau directeur vous disait d'extraire C # et de le remplacer par Python. En tant que développeurs, nous devons apprendre que les questions et si les questions doivent s'arrêter quelque part. Résolvez le problème, passez au suivant. Si jamais vous avez le temps, revenez-y ...
ozzy432836
2
Python ressemble plus à MATLAB en termes de syntaxe et d'utilisation. Il doit y avoir une très bonne raison de passer d'un langage sécurisé de type statique à un script mishmash comme python.
John Alexiou
5

Je suppose que vous ne faites pas référence aux mêmes objets

Object1 == Object2

Vous pensez peut-être faire une comparaison de mémoire entre les deux

memcmp(Object1, Object2, sizeof(Object.GetType())

Mais ce n'est même pas du vrai code en c # :). Étant donné que toutes vos données sont probablement créées sur le tas, la mémoire n'est pas contiguë et vous ne pouvez pas simplement comparer l'égalité de deux objets de manière agnostique. Vous allez devoir comparer chaque valeur, une à la fois, de manière personnalisée.

Envisagez d'ajouter l' IEquatable<T>interface à votre classe et définissez une Equalsméthode personnalisée pour votre type. Ensuite, dans cette méthode, testez manuellement chaque valeur. Ajoutez à IEquatable<T>nouveau les types inclus si vous le pouvez et répétez le processus.

class Foo : IEquatable<Foo>
{
  public bool Equals(Foo other)
  {
    /* check all the values */
    return false;
  }
}
payo
la source
5

Sérialisez les deux objets, puis calculez le code de hachage, puis comparez.

Sk Shahnawaz-ul Haque
la source
3

J'ai trouvé cette fonction ci-dessous pour comparer des objets.

 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 (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
         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;
 }

Je l'utilise et cela fonctionne très bien pour moi.

Akshay
la source
1
Le premier ifsignifie ce Compare(null, null) == falsequi n'est pas ce à quoi je m'attendais.
Tim Sylvester
3

Sur la base de quelques réponses déjà données ici, j'ai décidé de soutenir principalement la réponse de JoelFan . J'adore les méthodes d'extension et celles-ci fonctionnent très bien pour moi alors qu'aucune des autres solutions ne les utiliserait pour comparer mes classes complexes.

Méthodes d'extension

using System.IO;
using System.Xml.Serialization;

static class ObjectHelpers
{
    public static string SerializeObject<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

        using (StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static bool EqualTo(this object obj, object toCompare)
    {
        if (obj.SerializeObject() == toCompare.SerializeObject())
            return true;
        else
            return false;
    }

    public static bool IsBlank<T>(this T obj) where T: new()
    {
        T blank = new T();
        T newObj = ((T)obj);

        if (newObj.SerializeObject() == blank.SerializeObject())
            return true;
        else
            return false;
    }

}

Exemples d'utilisation

if (record.IsBlank())
    throw new Exception("Record found is blank.");

if (record.EqualTo(new record()))
    throw new Exception("Record found is blank.");
Arvo Bowen
la source
2

Je dirais que:

Object1.Equals(Object2)

serait ce que vous recherchez. C'est si vous cherchez à voir si les objets sont les mêmes, ce que vous semblez demander.

Si vous souhaitez vérifier si tous les objets enfants sont identiques, exécutez-les via une boucle avec la Equals()méthode.

PaulG
la source
2
Si et seulement s'ils fournissent une surcharge d'égalité non-référence de Equals.
user7116
Chaque classe doit implémenter sa propre méthode de comparaison. Si les classes de l'auteur n'ont pas de substitution pour la méthode Equals (), elles utiliseront la méthode de base de la classe System.Object (), ce qui entraînera des erreurs de logique.
Dima
2
public class GetObjectsComparison
{
    public object FirstObject, SecondObject;
    public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
    public FieldInfo SecondObjectFieldInfo;
    public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
    public bool ErrorFound;
    public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
    GetObjectsComparison FunctionGet = GetObjectsComparison;
    SetObjectsComparison FunctionSet = new SetObjectsComparison();
    if (FunctionSet.ErrorFound==false)
        foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
        {
            FunctionSet.SecondObjectFieldInfo =
            FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);

            FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
            FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
            if (FirstObjectFieldInfo.FieldType.IsNested)
            {
                FunctionSet.GetObjectsComparison =
                new GetObjectsComparison()
                {
                    FirstObject = FunctionSet.FirstObjectFieldInfoValue
                    ,
                    SecondObject = FunctionSet.SecondObjectFieldInfoValue
                };

                if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
                {
                    FunctionSet.ErrorFound = true;
                    break;
                }
            }
            else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
            {
                FunctionSet.ErrorFound = true;
                break;
            }
        }
    return !FunctionSet.ErrorFound;
}
matan justme
la source
Utilisation des principes de la récursivité.
matan justme le
1

Une façon de le faire serait de remplacer Equals()chaque type impliqué. Par exemple, votre objet de niveau supérieur remplacerait Equals()pour appeler la Equals()méthode des 5 objets enfants. Ces objets doivent également tous être remplacés Equals(), en supposant qu'il s'agit d'objets personnalisés, et ainsi de suite jusqu'à ce que toute la hiérarchie puisse être comparée en effectuant simplement une vérification d'égalité sur les objets de niveau supérieur.

gorique
la source
1

Utilisez l' IEquatable<T>interface qui a une méthode Equals.

Master chef
la source
1

Merci à l'exemple de Jonathan. Je l'ai développé pour tous les cas (tableaux, listes, dictionnaires, types primitifs).

Il s'agit d'une comparaison sans sérialisation et ne nécessite l'implémentation d'aucune interface pour les objets comparés.

        /// <summary>Returns description of difference or empty value if equal</summary>
        public static string Compare(object obj1, object obj2, string path = "")
        {
            string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
            if (obj1 == null && obj2 != null)
                return path1 + "null != not null";
            else if (obj2 == null && obj1 != null)
                return path1 + "not null != null";
            else if (obj1 == null && obj2 == null)
                return null;

            if (!obj1.GetType().Equals(obj2.GetType()))
                return "different types: " + obj1.GetType() + " and " + obj2.GetType();

            Type type = obj1.GetType();
            if (path == "")
                path = type.Name;

            if (type.IsPrimitive || typeof(string).Equals(type))
            {
                if (!obj1.Equals(obj2))
                    return path1 + "'" + obj1 + "' != '" + obj2 + "'";
                return null;
            }
            if (type.IsArray)
            {
                Array first = obj1 as Array;
                Array second = obj2 as Array;
                if (first.Length != second.Length)
                    return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";

                var en = first.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    string res = Compare(en.Current, second.GetValue(i), path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
            {
                System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
                System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;

                var en = first.GetEnumerator();
                var en2 = second.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    if (!en2.MoveNext())
                        return path + ": enumerable size differs";

                    string res = Compare(en.Current, en2.Current, path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else
            {
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    try
                    {
                        var val = pi.GetValue(obj1);
                        var tval = pi.GetValue(obj2);
                        if (path.EndsWith("." + pi.Name))
                            return null;
                        var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
                        string res = Compare(val, tval, pathNew);
                        if (res != null)
                            return res;
                    }
                    catch (TargetParameterCountException)
                    {
                        //index property
                    }
                }
                foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    var val = fi.GetValue(obj1);
                    var tval = fi.GetValue(obj2);
                    if (path.EndsWith("." + fi.Name))
                        return null;
                    var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
                    string res = Compare(val, tval, pathNew);
                    if (res != null)
                        return res;
                }
            }
            return null;
        }

Pour une copie facile du référentiel de code créé

Alexey Obukhov
la source
1

Vous pouvez maintenant utiliser json.net. Allez simplement sur Nuget et installez-le.

Et vous pouvez faire quelque chose comme ceci:

    public bool Equals(SamplesItem sampleToCompare)
    {
        string myself = JsonConvert.SerializeObject(this);
        string other = JsonConvert.SerializeObject(sampleToCompare);

        return myself == other;
    }

Vous pourriez peut-être créer une méthode d'extension pour object si vous voulez devenir plus sophistiqué. Veuillez noter que cela ne compare que les propriétés publiques. Et si vous souhaitez ignorer une propriété publique lorsque vous effectuez la comparaison, vous pouvez utiliser l'attribut [JsonIgnore].

pierre de taille64
la source
Si vous avez des listes dans vos objets et que celles-ci ont des listes, essayer de traverser les deux objets sera un cauchemar. Si vous sérialisez les deux puis comparez, vous n'aurez pas à gérer ce scénario.
ashlar64
Si votre objet complexe contient un dictionnaire, je ne pense pas que le sérialiseur .net puisse le sérialiser. Le sérialiseur Json peut.
ashlar64