Der beste Weg, um zwei komplexe Objekte zu vergleichen

112

Ich habe zwei komplexe Objekte wie Object1und Object2. Sie haben ungefähr 5 Ebenen von untergeordneten Objekten.

Ich brauche die schnellste Methode, um zu sagen, ob sie gleich sind oder nicht.

Wie könnte dies in C # 4.0 geschehen?

Entwickler
quelle

Antworten:

101

Implementieren Sie IEquatable<T>(normalerweise in Verbindung mit dem Überschreiben der geerbten Methoden Object.Equalsund Object.GetHashCodeMethoden) alle Ihre benutzerdefinierten Typen. Rufen Sie bei zusammengesetzten Typen die EqualsMethode der enthaltenen Typen innerhalb der enthaltenen Typen auf. Verwenden Sie für enthaltene Sammlungen die SequenceEqualErweiterungsmethode, die intern IEquatable<T>.Equalsoder Object.Equalsfür jedes Element aufgerufen wird. Bei diesem Ansatz müssen Sie natürlich die Definitionen Ihrer Typen erweitern, aber die Ergebnisse sind schneller als bei generischen Lösungen mit Serialisierung.

Bearbeiten : Hier ist ein erfundenes Beispiel mit drei Verschachtelungsebenen.

Bei Werttypen können Sie normalerweise nur deren EqualsMethode aufrufen . Selbst wenn die Felder oder Eigenschaften niemals explizit zugewiesen würden, hätten sie dennoch einen Standardwert.

Bei Referenztypen sollten Sie zuerst aufrufen ReferenceEquals, um die Referenzgleichheit zu überprüfen. Dies würde als Effizienzsteigerung dienen, wenn Sie zufällig auf dasselbe Objekt verweisen. Es würde auch Fälle behandeln, in denen beide Referenzen null sind. Wenn diese Prüfung fehlschlägt, bestätigen Sie, dass das Feld oder die Eigenschaft Ihrer Instanz nicht null ist (um dies zu vermeiden NullReferenceException), und rufen Sie die EqualsMethode auf. Da unsere Mitglieder ordnungsgemäß typisiert sind, wird die IEquatable<T>.EqualsMethode direkt aufgerufen, wobei die überschriebene Object.EqualsMethode umgangen wird (deren Ausführung aufgrund der Typumwandlung geringfügig langsamer wäre).

Wenn Sie überschreiben Object.Equals, wird auch erwartet, dass Sie überschreiben Object.GetHashCode. Ich habe dies im Folgenden aus Gründen der Übersichtlichkeit nicht getan.

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

Update : Diese Antwort wurde vor einigen Jahren geschrieben. Seitdem habe ich mich von der Implementierung IEquality<T>für veränderbare Typen für solche Szenarien abgewandt . Es gibt zwei Begriffe von Gleichheit: Identität und Äquivalenz . Auf der Ebene der Speicherrepräsentation werden diese im Volksmund als „Referenzgleichheit“ und „Wertgleichheit“ unterschieden (siehe Gleichheitsvergleiche ). Die gleiche Unterscheidung kann jedoch auch auf Domänenebene gelten. Angenommen, Ihre PersonKlasse hat eine PersonIdEigenschaft, die für jede Person in der realen Welt einzigartig ist. Sollten zwei Objekte mit gleichen, PersonIdaber unterschiedlichen AgeWerten als gleich oder unterschiedlich betrachtet werden? Die obige Antwort geht davon aus, dass man nach Äquivalenz strebt. Es gibt jedoch viele Verwendungen derIEquality<T>Schnittstelle wie Sammlungen, die davon ausgehen, dass solche Implementierungen für Identität sorgen . Wenn Sie beispielsweise a auffüllen HashSet<T>, erwarten Sie normalerweise, dass ein TryGetValue(T,T)Aufruf vorhandene Elemente zurückgibt, die lediglich die Identität Ihres Arguments teilen, nicht unbedingt äquivalente Elemente, deren Inhalt vollständig identisch ist. Dieser Begriff wird durch die Anmerkungen durchgesetzt GetHashCode:

Im Allgemeinen sollten Sie für veränderbare Referenztypen GetHashCode()nur überschreiben , wenn:

  • Sie können den Hash-Code aus Feldern berechnen, die nicht veränderbar sind. oder
  • Sie können sicherstellen, dass sich der Hash-Code eines veränderlichen Objekts nicht ändert, während das Objekt in einer Sammlung enthalten ist, die auf seinem Hash-Code basiert.
Douglas
quelle
Ich erhalte dieses Objekt über RIA Services ... Kann ich IEquatable <Foo> für diese Objekte verwenden und es unter dem WPF-Client abrufen?
Entwickler
1
Sie meinen, dass die Klassen automatisch generiert werden? Ich habe RIA Services nicht verwendet, aber ich würde davon ausgehen, dass alle generierten Klassen als deklariert werden. partialIn diesem Fall können Sie ihre EqualsMethode durch eine manuell hinzugefügte Teilklassendeklaration implementieren, die auf Felder / Eigenschaften aus der automatisch generierten Klasse verweist einer.
Douglas
Was wäre, wenn "Adresse Adresse" tatsächlich "Adresse [] Adressen" ist, wie würde es implementiert werden?
Guiomie
2
Sie können die LINQ- Enumerable.SequenceEqualMethode für die Arrays aufrufen : this.Addresses.SequenceEqual(other.Addresses). Dies würde Ihre Address.EqualsMethode intern für jedes Paar entsprechender Adressen aufrufen , vorausgesetzt, die AddressKlasse implementiert die IEquatable<Address>Schnittstelle.
Douglas
2
Eine andere Vergleichskategorie, die ein Entwickler überprüfen könnte, ist "WorksLike". Für mich bedeutet dies, dass das Programm zwar zwei ungleiche Eigenschaftswerte aufweist, das Programm jedoch bei der Verarbeitung der beiden Instanzen dasselbe Ergebnis erzielt.
John Kurtz
95

Serialisieren Sie beide Objekte und vergleichen Sie die resultierenden Zeichenfolgen

JoelFan
quelle
1
Ich verstehe nicht, warum es das geben würde. Die Serialisierung ist normalerweise ein optimierter Prozess, und Sie müssen in jedem Fall auf den Wert jeder Eigenschaft zugreifen.
JoelFan
5
Es gibt enorme Kosten. Sie generieren einen Datenstrom, hängen Zeichenfolgen an und testen dann die Zeichenfolgengleichheit. Größenordnungen, genau darin. Ganz zu schweigen von der Serialisierung, bei der standardmäßig Reflektion verwendet wird.
Jerome Haltom
2
Datenstrom ist keine große Sache, ich verstehe nicht, warum Sie Zeichenfolgen anhängen müssten ... das Testen der Zeichenfolgengleichheit ist eine der am besten optimierten Operationen da draußen ... Sie haben vielleicht einen Punkt mit Reflexion ... aber weiter Die gesamte Serialisierung wird nicht um "Größenordnungen" schlechter sein als andere Methoden. Sie sollten Benchmarks durchführen, wenn Sie Leistungsprobleme vermuten ... Ich habe keine Leistungsprobleme mit dieser Methode
festgestellt
12
Ich bin +1das einfach, weil ich nie darüber nachgedacht habe, auf diese Weise einen wertebasierten Gleichheitsvergleich durchzuführen. Es ist schön und einfach. Es wäre schön, einige Benchmarks mit diesem Code zu sehen.
Thomas
1
Dies ist keine gute Lösung, da beide Serialisierungen auf ähnliche Weise schief gehen können. Beispielsweise wurden einige Eigenschaften des Quellobjekts möglicherweise nicht serialisiert, und wenn sie deserialisiert werden, werden sie im Zielobjekt auf null gesetzt. In einem solchen Fall besteht Ihr Test, der Zeichenfolgen vergleicht, aber beide Objekte sind tatsächlich nicht gleich!
stackMeUp
35

Sie können die Erweiterungsmethode Rekursion verwenden, um dieses Problem zu beheben:

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

oder mit Json vergleichen (wenn das Objekt sehr komplex ist) Sie können Newtonsoft.Json verwenden:

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
quelle
1
Die erste Lösung ist großartig! Ich mag es, dass Sie json nicht serialisieren oder implementieren müssen, um den Objekten selbst Code hinzuzufügen. Geeignet, wenn Sie nur für Unit-Tests vergleichen. Darf ich vorschlagen, einen einfachen Vergleich hinzuzufügen, falls objValue und anotherValue beide gleich null sind? Dadurch wird vermieden, dass beim Versuch, null.Equals () // ReSharper zu deaktivieren, eine NullReferenceException ausgelöst wird. RedundantJumpStatement if (objValue == anotherValue) continue; // null Referenzschutz sonst if (! objValue.Equals (anotherValue)) Fail (erwartet, tatsächlich);
Mark Conway
3
Gibt es einen Grund zu verwenden, DeepCompareanstatt einfach CompareExrekursiv aufzurufen ?
Apostolow
3
Dies kann die gesamte Struktur unnötig vergleichen. Das Ersetzen resultdurch return falsewürde es effizienter machen.
Tim Sylvester
24

Wenn Sie IEquatable nicht implementieren möchten, können Sie jederzeit Reflection verwenden, um alle Eigenschaften zu vergleichen: - Wenn es sich um einen Werttyp handelt, vergleichen Sie sie einfach. - Wenn es sich um einen Referenztyp handelt, rufen Sie die Funktion rekursiv auf, um ihre "inneren" Eigenschaften zu vergleichen .

Ich denke nicht an Leistung, sondern an Einfachheit. Dies hängt jedoch von der genauen Gestaltung Ihrer Objekte ab. Abhängig von der Form Ihres Objekts kann dies kompliziert werden (z. B. wenn zwischen den Eigenschaften zyklische Abhängigkeiten bestehen). Es gibt jedoch mehrere Lösungen, die Sie verwenden können, wie diese:

Eine andere Möglichkeit besteht darin, das Objekt als Text zu serialisieren, beispielsweise mit JSON.NET, und das Serialisierungsergebnis zu vergleichen. (JSON.NET kann zyklische Abhängigkeiten zwischen Eigenschaften verarbeiten).

Ich weiß nicht, ob Sie mit "am schnellsten" den schnellsten Weg zur Implementierung oder einen schnell laufenden Code meinen. Sie sollten nicht optimieren, bevor Sie wissen, ob Sie dies benötigen. Vorzeitige Optimierung ist die Wurzel allen Übels

JotaBe
quelle
1
Ich denke kaum, dass eine IEquatable<T>Implementierung als Fall einer vorzeitigen Optimierung qualifiziert ist. Die Reflexion wird drastisch langsamer sein. Die Standardimplementierung Equalsfür benutzerdefinierte Werttypen verwendet Reflection. Microsoft selbst empfiehlt, es für die Leistung zu überschreiben: "Überschreiben Sie die EqualsMethode für einen bestimmten Typ, um die Leistung der Methode zu verbessern und das Konzept der Gleichheit für den Typ genauer darzustellen."
Douglas
1
Es hängt davon ab, wie oft er die Gleichheitsmethode ausführen wird: 1, 10, 100, 100, eine Million? Das wird einen großen Unterschied machen. Wenn er eine generische Lösung verwenden kann, ohne etwas zu implementieren, wird er wertvolle Zeit sparen. Wenn es zu langsam ist, ist es Zeit, IEquatable zu implementieren (und vielleicht sogar zu versuchen, einen zwischenspeicherbaren oder intelligenten GetHashCode zu erstellen). Was die Geschwindigkeit der Reflexion betrifft, muss ich zustimmen, dass es langsamer ist ... oder viel langsamer, je nachdem, wie Sie es tun ( dh Wiederverwendung von PropertyInfos-Typen usw. oder nicht).
JotaBe
@ Worthy7 Das tut es. Bitte beachten Sie den Inhalt des Projekts. Tests sind eine gute Möglichkeit, anhand von Beispielen zu dokumentieren. Aber besser als das, wenn Sie danach suchen, finden Sie eine .chm-Hilfedatei. Dieses Projekt hat also eine viel bessere Dokumentation als die meisten Projekte.
JotaBe
Entschuldigung, Sie haben Recht, ich habe den Tab "Wiki" total verpasst. Ich bin es gewohnt, dass jeder Dinge in die Readme schreibt.
Worthy7
9

Serialisieren Sie beide Objekte und vergleichen Sie die resultierenden Zeichenfolgen mit @JoelFan

Erstellen Sie dazu eine statische Klasse wie diese und erweitern Sie ALLE Objekte mit Erweiterungen (damit Sie einen beliebigen Objekttyp, eine Sammlung usw. an die Methode übergeben können).

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

Sobald Sie diese statische Klasse in einer anderen Datei referenzieren, können Sie Folgendes tun:

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

Jetzt können Sie sie einfach mit .Equals vergleichen. Ich benutze dies, um zu überprüfen, ob sich Objekte auch in Sammlungen befinden. Es funktioniert wirklich gut.

ozzy432836
quelle
Was ist, wenn der Inhalt der Objekte Arrays von Gleitkommazahlen sind? Es ist sehr ineffizient, diese in Zeichenfolgen zu konvertieren, und die Konvertierung unterliegt den in definierten Transformationen CultrureInfo. Dies würde nur funktionieren, wenn die internen Daten hauptsächlich Zeichenfolgen und Ganzzahlen sind. Sonst wäre es eine Katastrophe.
John Alexiou
3
Was ist, wenn ein neuer Regisseur Ihnen sagt, Sie sollen C # herausreißen und durch Python ersetzen? Als Entwickler müssen wir lernen, dass die Was-wäre-wenn-Fragen irgendwo aufhören müssen. Lösen Sie das Problem mit dem nächsten. Wenn Sie jemals Zeit bekommen, kommen Sie zurück ...
ozzy432836
2
Python ähnelt in Syntax und Verwendung eher MATLAB. Es muss einen wirklich guten Grund geben, von einer statischen Sprache zu einem Mischmaschenskript wie Python zu wechseln.
John Alexiou
5

Ich gehe davon aus, dass Sie sich nicht buchstäblich auf dieselben Objekte beziehen

Object1 == Object2

Sie könnten darüber nachdenken, einen Speichervergleich zwischen den beiden durchzuführen

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

Aber das ist nicht einmal echter Code in c # :). Da wahrscheinlich alle Ihre Daten auf dem Heap erstellt werden, ist der Speicher nicht zusammenhängend und Sie können die Gleichheit zweier Objekte nicht einfach agnostisch vergleichen. Sie müssen jeden Wert einzeln auf benutzerdefinierte Weise vergleichen.

Erwägen Sie, die IEquatable<T>Schnittstelle zu Ihrer Klasse hinzuzufügen , und definieren Sie eine benutzerdefinierte EqualsMethode für Ihren Typ. Testen Sie dann bei dieser Methode jeden Wert manuell. Fügen IEquatable<T>Sie nach Möglichkeit erneut beiliegende Typen hinzu und wiederholen Sie den Vorgang.

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

Serialisieren Sie beide Objekte, berechnen Sie den Hash-Code und vergleichen Sie ihn.

Sk Shahnawaz-ul Haque
quelle
3

Ich habe diese Funktion zum Vergleichen von Objekten gefunden.

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

Ich benutze es und es funktioniert gut für mich.

Akshay
quelle
1
Das erste ifbedeutet das Compare(null, null) == false, was ich nicht erwarten würde.
Tim Sylvester
3

Aufgrund einiger Antworten, die hier bereits gegeben wurden, entschied ich mich, JoelFans Antwort größtenteils zu unterstützen . Ich liebe Erweiterungsmethoden und diese haben für mich hervorragend funktioniert, wenn keine der anderen Lösungen sie zum Vergleichen meiner komplexen Klassen verwenden würde.

Erweiterungsmethoden

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

}

Anwendungsbeispiele

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

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

Ich würde sagen, dass:

Object1.Equals(Object2)

wäre das, wonach du suchst. Das ist, wenn Sie sehen möchten, ob die Objekte gleich sind, was Sie anscheinend fragen.

Wenn Sie überprüfen möchten, ob alle untergeordneten Objekte identisch sind, führen Sie sie mit der Equals()Methode durch eine Schleife .

PaulG
quelle
2
Genau dann, wenn sie eine Nichtreferenz-Gleichheitsüberladung von Gleichen liefern.
user7116
Jede Klasse muss ihre eigene Vergleichsmethode implementieren. Wenn die Klassen des Autors keine Überschreibungen für die Equals () -Methode haben, verwenden sie die grundlegende Methode der System.Object () -Klasse, was zu Fehlern in der Logik führt.
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
quelle
Verwendung der Prinzipien der Rekursion.
Matan Justme
1

Eine Möglichkeit, dies zu tun, besteht darin, Equals()jeden beteiligten Typ zu überschreiben . Beispielsweise würde Ihr Objekt der obersten Ebene überschrieben Equals(), um die Equals()Methode aller 5 untergeordneten Objekte aufzurufen . Diese Objekte sollten ebenfalls alle überschrieben Equals()werden, vorausgesetzt, es handelt sich um benutzerdefinierte Objekte usw., bis die gesamte Hierarchie verglichen werden kann, indem nur eine Gleichheitsprüfung für die Objekte der obersten Ebene durchgeführt wird.

goric
quelle
1

Verwenden Sie eine IEquatable<T>Schnittstelle mit einer Methode Equals.

Master Chief
quelle
1

Dank dem Beispiel von Jonathan. Ich habe es für alle Fälle erweitert (Arrays, Listen, Wörterbücher, primitive Typen).

Dies ist ein Vergleich ohne Serialisierung und erfordert keine Implementierung von Schnittstellen für verglichene Objekte.

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

Zum einfachen Kopieren des von Code erstellten Repositorys

Alexey Obukhov
quelle
1

Sie können jetzt json.net verwenden. Gehen Sie einfach auf Nuget und installieren Sie es.

Und Sie können so etwas tun:

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

        return myself == other;
    }

Sie könnten vielleicht eine Erweiterungsmethode für ein Objekt erstellen, wenn Sie schicker werden möchten. Bitte beachten Sie, dass hier nur die öffentlichen Objekte verglichen werden. Wenn Sie beim Vergleich eine öffentliche Eigenschaft ignorieren möchten, können Sie das Attribut [JsonIgnore] verwenden.

ashlar64
quelle
Wenn Ihre Listen Listen enthalten und diese Listen enthalten, ist der Versuch, beide Objekte zu durchlaufen, ein Albtraum. Wenn Sie beide serialisieren und dann vergleichen, müssen Sie sich nicht mit diesem Szenario befassen.
ashlar64
Wenn Ihr komplexes Objekt ein Wörterbuch enthält, glaube ich nicht, dass der .net-Serializer es serialisieren kann. Der Json Serializer kann.
ashlar64