Best Way to Compare Two Complex Objects

Best way to compare two complex objects

Implement IEquatable<T> (typically in conjunction with overriding the inherited Object.Equals and Object.GetHashCode methods) on all your custom types. In the case of composite types, invoke the contained types’ Equals method within the containing types. For contained collections, use the SequenceEqual extension method, which internally calls IEquatable<T>.Equals or Object.Equals on each element. This approach will obviously require you to extend your types’ definitions, but its results are faster than any generic solutions involving serialization.

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

For value types, you can typically just call their Equals method. Even if the fields or properties were never explicitly assigned, they would still have a default value.

For reference types, you should first call ReferenceEquals, which checks for reference equality – this would serve as an efficiency boost when you happen to be referencing the same object. It would also handle cases where both references are null. If that check fails, confirm that your instance's field or property is not null (to avoid NullReferenceException) and call its Equals method. Since our members are properly typed, the IEquatable<T>.Equals method gets called directly, bypassing the overridden Object.Equals method (whose execution would be marginally slower due to the type cast).

When you override Object.Equals, you’re also expected to override Object.GetHashCode; I didn’t do so below for the sake of conciseness.

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 several years ago. Since then, I've started to lean away from implementing IEquality<T> for mutable types for such scenarios. There are two notions of equality: identity and equivalence. At a memory representation level, these are popularly distinguished as “reference equality” and “value equality” (see Equality Comparisons). However, the same distinction can also apply at a domain level. Suppose that your Person class has a PersonId property, unique per distinct real-world person. Should two objects with the same PersonId but different Age values be considered equal or different? The answer above assumes that one is after equivalence. However, there are many usages of the IEquality<T> interface, such as collections, that assume that such implementations provide for identity. For example, if you're populating a HashSet<T>, you would typically expect a TryGetValue(T,T) call to return existing elements that share merely the identity of your argument, not necessarily equivalent elements whose contents are completely the same. This notion is enforced by the notes on GetHashCode:

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

  • You can compute the hash code from fields that are not mutable; or
  • You can ensure that the hash code of a mutable object does not change while the object is contained in a collection that relies on its hash code.

Compare two class objects in C#

Assert.Equal relies on the object's .Equals method (see here). When creating your own classes, you have an option to override this method and add your own logic of comparing two objects equality (e.g., field by field equality checking).

If you do not override it, there is a default .Equals inherited from Object, which is equivalent to .ReferenceEquals: it returns true if the two objects have the same reference and false otherwise. The only situation when the two objects have the same reference is when it's the same object.

In your case, you can either define the .Equals override for your class (e.g., see here) or you can try to follow some of the advice given in this SO answer on how to perform deep equality checking of two objects. Third option is to create a separate class inherited from IEqualityComparer, which will purely provide the equality checking logic for your object while not being a part of your class codebase (for example, see this article).

Compare complex object and get difference C# .NET Core 3.1

Install package from nugget
Install-Package ObjectsComparer

And then imply compare :
var cmpr = new Comparer();
/* compare
isSame is the booleab variable and will return true if both objects are same
*/
IEnumerable diff;

bool isSame = cmpr.Compare(obj1, obj2, out diff);

Javascript compare two complex objects

I have done the comparison based on the inputs you give and its work well.

But I would recommend some structural changes to your answer array and the question array, That will improve the performance. This structure help you to scale the system very easily.

  1. The answer array need to structured like below

    [{ "Qid": 1, "answer" : "user selected answer"}, { "Qid": 2, "answer" : "user selected answer"}]

  2. The question array should change like below

     [{Qid: 1, Question: "This is question 1",
    Answers:[{"option":"Answer1",IsCorrect:false},
    {"option":"Answer2",IsCorrect:true},{"option":"Answer3",IsCorrect:false}
    {"option":"Answer4",IsCorrect:false}]}]





let userAnswers = [{Qid: 1, Question: "This is question 1", Answers:[{Ans1:"Answer1",IsCorrect:false}, {Ans2:"Answer2",IsCorrect:false}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]},{Qid: 2, Question: "This is question 2", Answers:[{Ans1:"Answer1",IsCorrect:false}, {Ans2:"Answer2",IsCorrect:true}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]},{Qid: 3, Question: "This is question 3", Answers:[{Ans1:"Answer1",IsCorrect:true}, {Ans2:"Answer2",IsCorrect:false}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]},{Qid: 4, Question: "This is question 4", Answers:[{Ans1:"Answer1",IsCorrect:false}, {Ans2:"Answer2",IsCorrect:false}, {Ans3:"Answer3",IsCorrect:true}, {Ans4:"Answer4",IsCorrect:false}]}];


let keyquestions = [{Qid: 1, Question: "This is question 1", Answers:[{Ans1:"Answer1",IsCorrect:false}, {Ans2:"Answer2",IsCorrect:true}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]},{Qid: 2, Question: "This is question 2", Answers:[{Ans1:"Answer1",IsCorrect:false}, {Ans2:"Answer2",IsCorrect:true}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]},{Qid: 3, Question: "This is question 3", Answers:[{Ans1:"Answer1",IsCorrect:true}, {Ans2:"Answer2",IsCorrect:false}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]},{Qid: 4, Question: "This is question 4", Answers:[{Ans1:"Answer1",IsCorrect:false}, {Ans2:"Answer2",IsCorrect:true}, {Ans3:"Answer3",IsCorrect:false}, {Ans4:"Answer4",IsCorrect:false}]}];
let result = {
"right" : 0, "wrong" : 0, "unattempted" :0
};


result = userAnswers.reduce((res, i) => {
let user = i.Answers.filter(a => !!a.IsCorrect);
let question = keyquestions.filter(k => k.Qid == i.Qid)[0];
let actualAnswer = question.Answers.filter(a => !!a.IsCorrect)[0];
if(!!user && user.length) {
user[Object.keys(user)[0]] == actualAnswer[Object.keys(actualAnswer)[0]] ? res.right++ : res.wrong++;
} else {
res.unattempted++;
}
return res;
}, result);

console.log(result);

Compare two known Complex type object without using reflection

Simply put, you can't... Unless the objects already implement an Equals method :-) There are no magic fairies that will compare objects in .NET .

Technically there is a third way... It goes totally against what you literally wrote, but is normally considered to be "acceptable": write a T4 template that, using reflection, generates a .cs file containing the comparator (that compares the objects using manual property comparison). The T4 is run at compile time, and the .cs generated is generated at compile time (and compiled), so there is no reflection at runtime, and there is no "human written" manual property comparison.

Note that doing this T4 template correctly is very complex... It is a project inside a project!

There are many ways to do it, each one with its pitfalls. Just deciding what to compare (fields or properties) is a problem enough. And for the fields/properties, do you want to compare everything with an option to exclude something, or nothing with an option to include what you really want to compare? Then deciding what to do with collections (do you want to compare their elements?). Many decisions to take!

c# Object Comparison With Complex Objects

Calling the method recursively is the issue here.

If we call a method DetailedCompare recursively passing as parameters two objects all its fine - as we can get their properties and compare them.

However when we call DetailedCompare recursively passing a two list of objects - we can not just get the properties of those lists - but we need to traverse and get the properties of those list and compare their value.

IMHO it would be better to separate the logic using a helper method - so when we find a nested list - we can tackle the logic as I have described above.

This is the Extension class I have written

  public static class Extension
{
public static List<Variance> Variances { get; set; }

static Extension()
{
Variances = new List<Variance>();
}

public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var v = new Variance
{
PropertyName = property.Name,
ValA = property.GetValue(val1, null),
ValB = property.GetValue(val2, null)
};

if (v.ValA == null && v.ValB == null)
{
continue;
}

if (v.ValA is ICollection)
{
Console.WriteLine("I found a nested list");
DetailedCompareList(v.ValA,v.ValB);
}
else if (v.ValA != null && !v.ValA.Equals(v.ValB))
{
Variances.Add(v);
}
}

return Variances;
}

private static void DetailedCompareList<T>(T val1, T val2)
{
if (val1 is ICollection collection1 && val2 is ICollection collection2)
{
var coll1 = collection1.Cast<object>().ToList();
var coll2 = collection2.Cast<object>().ToList();

for (int j = 0; j < coll1.Count; j++)
{
Type type = coll1[j].GetType();
PropertyInfo[] propertiesOfCollection1 = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] propertiesOfCollection2 = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

for (int i = 0; i < propertiesOfCollection1.Length; i++)
{
var variance = new Variance
{
PropertyName = propertiesOfCollection1[i].Name,
ValA = propertiesOfCollection1[i].GetValue(coll1[j]),
ValB = propertiesOfCollection2[i].GetValue(coll2[j])
};

if (!variance.ValA.Equals(variance.ValB))
{
Variances.Add(variance);
}
}
}
}
}
}

With the following result:
Sample Image

Limitations
This approach is bounded by the definition of your objects - hence it can only work with 1 level of depth.

Compare the content of two objects for equality


Is there a generic way to compare the content of the two objects?


Well yes, but generally that's known as the IComparable interface.

If you could descend from the class and create a child that implemented IComparable, that might be ideal.



Related Topics



Leave a reply



Submit