Ich habe zwei komplexe Objekte wie Object1
und 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?
Implementieren Sie IEquatable<T>
(normalerweise in Verbindung mit dem Überschreiben der geerbten Methoden Object.Equals
und Object.GetHashCode
Methoden) alle Ihre benutzerdefinierten Typen. Rufen Sie bei zusammengesetzten Typen die Equals
Methode der enthaltenen Typen innerhalb der enthaltenen Typen auf. Verwenden Sie für enthaltene Sammlungen die SequenceEqual
Erweiterungsmethode, die intern IEquatable<T>.Equals
oder Object.Equals
fü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 Equals
Methode 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 Equals
Methode auf. Da unsere Mitglieder ordnungsgemäß typisiert sind, wird die IEquatable<T>.Equals
Methode direkt aufgerufen, wobei die überschriebene Object.Equals
Methode 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 Person
Klasse hat eine PersonId
Eigenschaft, die für jede Person in der realen Welt einzigartig ist. Sollten zwei Objekte mit gleichen, PersonId
aber unterschiedlichen Age
Werten 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.
partial
In diesem Fall können Sie ihreEquals
Methode durch eine manuell hinzugefügte Teilklassendeklaration implementieren, die auf Felder / Eigenschaften aus der automatisch generierten Klasse verweist einer.Enumerable.SequenceEqual
Methode für die Arrays aufrufen :this.Addresses.SequenceEqual(other.Addresses)
. Dies würde IhreAddress.Equals
Methode intern für jedes Paar entsprechender Adressen aufrufen , vorausgesetzt, dieAddress
Klasse implementiert dieIEquatable<Address>
Schnittstelle.Serialisieren Sie beide Objekte und vergleichen Sie die resultierenden Zeichenfolgen
quelle
+1
das 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.Sie können die Erweiterungsmethode Rekursion verwenden, um dieses Problem zu beheben:
oder mit Json vergleichen (wenn das Objekt sehr komplex ist) Sie können Newtonsoft.Json verwenden:
quelle
DeepCompare
anstatt einfachCompareEx
rekursiv aufzurufen ?result
durchreturn false
würde es effizienter machen.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
quelle
IEquatable<T>
Implementierung als Fall einer vorzeitigen Optimierung qualifiziert ist. Die Reflexion wird drastisch langsamer sein. Die StandardimplementierungEquals
für benutzerdefinierte Werttypen verwendet Reflection. Microsoft selbst empfiehlt, es für die Leistung zu überschreiben: "Überschreiben Sie dieEquals
Methode für einen bestimmten Typ, um die Leistung der Methode zu verbessern und das Konzept der Gleichheit für den Typ genauer darzustellen."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).
Sobald Sie diese statische Klasse in einer anderen Datei referenzieren, können Sie Folgendes tun:
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.
quelle
CultrureInfo
. Dies würde nur funktionieren, wenn die internen Daten hauptsächlich Zeichenfolgen und Ganzzahlen sind. Sonst wäre es eine Katastrophe.Ich gehe davon aus, dass Sie sich nicht buchstäblich auf dieselben Objekte beziehen
Sie könnten darüber nachdenken, einen Speichervergleich zwischen den beiden durchzuführen
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 benutzerdefinierteEquals
Methode für Ihren Typ. Testen Sie dann bei dieser Methode jeden Wert manuell. FügenIEquatable<T>
Sie nach Möglichkeit erneut beiliegende Typen hinzu und wiederholen Sie den Vorgang.quelle
Serialisieren Sie beide Objekte, berechnen Sie den Hash-Code und vergleichen Sie ihn.
quelle
Ich habe diese Funktion zum Vergleichen von Objekten gefunden.
Ich benutze es und es funktioniert gut für mich.
quelle
if
bedeutet dasCompare(null, null) == false
, was ich nicht erwarten würde.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
Anwendungsbeispiele
quelle
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 .quelle
quelle
Eine Möglichkeit, dies zu tun, besteht darin,
Equals()
jeden beteiligten Typ zu überschreiben . Beispielsweise würde Ihr Objekt der obersten Ebene überschriebenEquals()
, um dieEquals()
Methode aller 5 untergeordneten Objekte aufzurufen . Diese Objekte sollten ebenfalls alle überschriebenEquals()
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.quelle
Verwenden Sie eine
IEquatable<T>
Schnittstelle mit einer MethodeEquals
.quelle
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.
Zum einfachen Kopieren des von Code erstellten Repositorys
quelle
Sie können jetzt json.net verwenden. Gehen Sie einfach auf Nuget und installieren Sie es.
Und Sie können so etwas tun:
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.
quelle