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?
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?
Antworten:
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.
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.
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.
Serialisieren Sie beide Objekte und vergleichen Sie die resultierenden Zeichenfolgen
+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.
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;
}
DeepCompareanstatt einfach CompareExrekursiv aufzurufen ?
resultdurch return falsewü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
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."
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.
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
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;
}
}
Serialisieren Sie beide Objekte, berechnen Sie den Hash-Code und vergleichen Sie ihn.
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.
ifbedeutet das Compare(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.
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;
}
}
if (record.IsBlank())
throw new Exception("Record found is blank.");
if (record.EqualTo(new record()))
throw new Exception("Record found is blank.");
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 .
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;
}
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.
Verwenden Sie eine IEquatable<T>Schnittstelle mit einer Methode Equals.
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
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.