The best way to compare two complex objects

I have two complex objects, for example Object1 and Object2 . They have about 5 levels of child objects.

I need the fastest way to tell if they are the same or not.

How can this be done in C # 4.0?

+86
c #
May 04 '12 at 18:50
source share
15 answers

IEquatable<T> (usually in combination with overriding the inherited Object.Equals and Object.GetHashCode ) for all of your custom types. For composite types, call the Equals type method within the contained types. For contained collections, use the SequenceEqual extension method, which internally calls IEquatable<T>.Equals or Object.Equals for each element. This approach will obviously require you to extend the types of type definitions, but its results are faster than any general solutions involving serialization.

Edit : Here is a contrived example with three levels of nesting.

For value types, you can usually just call your Equals method. Even if fields or properties have never been explicitly assigned, they will still have a default value.

For reference types, you must first call ReferenceEquals , which checks for reference equality - this will serve as an increase in efficiency when you reference the same object. It will also handle cases where both links are null. If this NullReferenceException check fails, confirm that the field or property of your instance is not null (to avoid a NullReferenceException ) and call its Equals method. Since our members are correctly typed, the IEquatable<T>.Equals directly, bypassing the overridden Object.Equals method (which will be slightly slower due to the cast type).

When you override Object.Equals , you must also override Object.GetHashCode ; I did not do this below for the sake of brevity.

 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 : This answer was written a few years ago. Since then, I began to move away from implementing IEquality<T> for mutable types for such scenarios. There are two concepts of equality: identity and equivalence . At the level of memory representation, they are widely distinguished as “referential equality” and “equal value” (see “ Comparison of Equalities” ). However, the same difference may also apply at the domain level. Suppose your Person class has a PersonId property that is unique to an individual person in the real world. Should two objects with the same PersonId values ​​but different Age values ​​be considered equal or different? The above answer assumes one after equivalence. However, there are many IEquality<T> interface, such as collections, which assume that such implementations provide identification. For example, if you TryGetValue(T,T) HashSet<T> , you usually expect TryGetValue(T,T) to return existing elements that share only the identity of your argument, not necessarily equivalent elements whose contents are exactly the same. This concept is respected by the notes in GetHashCode :

In general, for mutable link types, you should override GetHashCode() only if:

  • You can calculate the hash code from fields that do not change; or
  • You can make sure that the hash code of the object being modified does not change while the object is contained in a collection that relies on its hash code.
+85
May 04 '12 at 18:53
source share

Serialize both objects and compare received strings

+71
Oct 13 '14 at 9:51 on
source share

You can use the extension method, recursion to solve this problem:

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

or compare using Json (if the object is very complex) you can use 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; } 
+22
Mar 21 '16 at 7:27
source share

If you do not want to implement IEquatable, you can always use Reflection to compare all properties: - if they are a value type, just compare them -if, they are a reference type, call the function recursively to compare its "internal" properties.

I do not think about performance, but about simplicity. It depends, however, on the exact design of your objects. This can be complicated depending on the shape of your objects (for example, if there are cyclic dependencies between the properties). However, there are several solutions that you can use, for example:

Another option is to serialize the object as text, for example, using JSON.NET, and compare the results of serialization. (JSON.NET can handle circular dependencies between properties).

I do not know if you mean the fastest way to implement it or code that works fast. You do not have to optimize before you know if you need to. Premature optimization is the root of all evil

+18
May 04 '12 at 19:32
source share

Serialize both objects and compare the resulting strings with @JoelFan

To do this, create such a static class and use Extensions to extend ALL objects (so that you can pass any type of object, collection, etc. to the method)

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

Once you reference this static class in any other file, you can do this:

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

Now you can simply use .Equals to compare them. I use this to check if objects are in collections. It works very well.

+9
Jan 20 '16 at 11:27
source share

I assume that you do not mean literally the same objects

 Object1 == Object2 

Perhaps you are thinking of comparing memory between two

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

But this is not even real code in C # :). Since all your data is probably created on the heap, memory is not contiguous, and you cannot just compare the equality of two objects agnostically. You will have to compare each value one at a time, in your own way.

Consider adding an IEquatable<T> interface to your class and defining a custom Equals method for your type. Then, in this method, each value is manually checked. Add IEquatable<T> again to the private types if you can and repeat the process.

 class Foo : IEquatable<Foo> { public bool Equals(Foo other) { /* check all the values */ return false; } } 
+5
May 04 '12 at 18:53
source share

I found this function below to compare objects.

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

I use it and it works great for me.

+3
May 20 '16 at 11:46
source share
 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; } 
+2
Jun 09 '17 at 14:29
source share

One way to do this is to override Equals() for each type used. For example, your top-level object will override Equals() to call the Equals() method for all 5 child objects. These objects should also override Equals() , assuming they are custom objects, and so on, until you compare the entire hierarchy by simply performing an equality check on top-level objects.

+1
May 4 '12 at 18:55
source share

Use the IEquatable<T> Interface, which has an Equals method.

+1
May 4 '12 at 18:58
source share

Based on several answers already given here, I decided to basically return JoelFan's answer . I like extension methods, and they are great for me when none of the other solutions use them to compare my complex classes.

Extension Methods

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

Examples of using

 if (record.IsBlank()) throw new Exception("Record found is blank."); if (record.EqualTo(new record())) throw new Exception("Record found is blank."); 
+1
Dec 05 '18 at 3:25
source share

Thanks to the example of Jonathan. I expanded it for all cases (arrays, lists, dictionaries, primitive types).

This comparison is without serialization and does not require the implementation of any interfaces for the compared objects.

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

A repository has been created for convenient code copying.

+1
Jan 16 '19 at 16:43
source share

I would say that:

Object1.Equals(Object2)

will be what you are looking for. This is if you want to see if the objects match what you seem to ask.

If you want to check if all child objects match, start them through the loop using the Equals() method.

0
May 04 '12 at 18:53
source share

Serialize both objects, then calculate the hash code, then compare.

0
May 24 '19 at 9:34
source share

A very simple way:

  • Create a list of your object;
  • If the intersection of these lists is false, then they are equal. Plain:
  • Link: using System.Linq;

     class Program { public class JohnsonObject { public int Id { get; set; } public string Description { get; set; } } static void Main() { JohnsonObject[] obj01 = new[] { new JohnsonObject { Id = 1, Description = "01" } }; JohnsonObject[] obj02 = new[] { new JohnsonObject { Id = 1, Description = "01" } }; Console.Write("Are equal? = {0}", !obj01.Intersect(obj02).Any()); } } 
-four
Sep 16 '16 at 17:40
source share



All Articles