C# Implementation of Deep/Recursive Object Comparison in .Net 3.5

C# implementation of deep/recursive object comparison in .net 3.5

I found a really nice, free implementation at www.kellermansoftware.com called Compare .NET Objects which can be found here. Highly recommended.


Appears to have relocated to github - most recent version is available here

Generically comparing properties of objects, with a little recursion to boot

Since you're dealing with types at runtime, you should have a non-generic version of your function which takes a Type parameter:

private bool HasPropertyChanged<T>(T Original, T Modified, string[] IgnoreProperties)
{
return HasPropertyChanged(typeof(T), Original, Modified, IgnoreProperties);
}

private bool HasPropertyChanged(Type T, object Original, object Modified, string[] IgnoreProperties)
{
// ...
}

With that, you can call the method for the list elements:

if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<>))
{
object val1 = p.GetValue(Original, null);
object val2 = p.GetValue(Modified, null);

// First check count...
PropertyInfo countProperty = p.PropertyType.GetProperty("Count");
int count1 = countProperty.GetValue(val1, null);
int count2 = countProperty.GetValue(val2, null);
if (count1 != count2) return true;

// Now iterate:
var enumerator1 = (val1 as System.Collections.IEnumerable).GetEnumerator();
var enumerator2 = (val2 as System.Collections.IEnumerable).GetEnumerator();
while (enumerator1.MoveNext())
{
enumerator2.MoveNext();
// check for null, skipping here...
object elem1 = enumerator1.Current;
object elem2 = enumerator2.Current;
if (HasPropertyChanged(elem1.GetType(), elem1, elem2, IgnoreProperties)) return true;
}
}
// ...

Recursively Compare Two Object Arrays

cheers for your answers i have managed to come up the the following method:

private bool Compare(Type type, string[] propertyNames, object[] oldState, object[] state) {
// Get the property indexes to ignore
var propertyIndexesToIgnore = type.GetProperties()
.Where(p => p.GetCustomAttributes(typeof(IgnoreLoggingAttribute), false).Count() > 0)
.Select(p => Array.IndexOf(propertyNames, p.Name)).ToArray();

// Get the child property indexes
var childPropertyIndexes = type.GetProperties()
.Where(p => p.GetCustomAttributes(typeof(ChildLoggingAttribute), false).Count() > 0)
.Select(p => Array.IndexOf(propertyNames, p.Name)).ToArray();

for (var i = 0; i < oldState.Length; i++) {
// If we need to check the child properties
if (childPropertyIndexes.Contains(i)) {
if (oldState[i] == null)
break;

var childPropertyType = oldState[i].GetType();
var childProperties = oldState[i].GetType().GetProperties();

// Recursively call this function to check the child properties
if (Compare(childPropertyType, childProperties.Select(p => p.Name).ToArray(), childProperties.Select(p => p.GetValue(oldState[i], null)).ToArray<object>(), childProperties.Select(p => p.GetValue(state[i], null)).ToArray<object>()))
return true;
} else if (!propertyIndexesToIgnore.Contains(i) && ((oldState[i] != null && state[i] != null && !oldState[i].Equals(state[i])) || (oldState[i] != null && state[i] == null) || (oldState[i] == null && state[i] != null)))
return true;
}

return false;
}

A few things to note:

  1. The amount of properties in my initial object array doesn't match the number of items in type.GetProperties(). I can't see how i can say whether an object within the object array has the IgnoreLogging attribute so i compare it against the type.
  2. The ChildLoggingAttribute is used to determine when an attribute is a complex type.

If anyone has any suggestions on how i can improve this then i'd really appreciate it. Thanks

Check for identical objects

Your product class should implement IEquatable<Product> interface for the Contains method to work properly. For example:

public class Product : IEquatable<Product>
{
public string Name { get; set; }

public double Price { get; set; }

public bool Equals(Product other)
{
return Name.Equals(other.Name)
&& Price.Equals(other.Price);
}
}

If you are using .NET 5 & C# 9, you can make your Product class a record. Then there will be no need to implement IEquatable<Product> explicitly.

Complex object comparison in C#

Okay, so you want deep unordered structural comparison. The "unordered" part is tricky, and in fact it is a strong hint that your classes are not designed right: List<T> is inherently ordered, so perhaps you would rather want to use a HashSet<T> there (if you don't expect to have any duplicates). Doing so would make the comparison both easier to implement, and faster (though insertions would be slower):

class Package
{
public HashSet<GroupList> groupList;

public override bool Equals(object o)
{
Package p = o as Package;
if (p == null) return false;
return groupList.SetEquals(p.groupList);
}

public override int GetHashCode()
{
return groupList.Aggregate(0, (hash, g) => hash ^ g.GetHashCode());
}
}

class GroupList
{
public HashSet<Feature> featureList;

public override bool Equals(object o)
{
GroupList g = o as GroupList;
if (g == null) return false;
return featureList.SetEquals(g.featureList);
}

public override int GetHashCode()
{
return featureList.Aggregate(0, (hash, f) => hash ^ f.GetHashCode());
}
}

class Feature
{
public int qty;

public override bool Equals(object o)
{
Feature f = o as Feature;
if (f == null) return false;
return qty == f.qty;
}

public override int GetHashCode()
{
return qty.GetHashCode();
}
}

If you want to keep using List<T>, you'll need to use LINQ set operations - note, however, that those are significantly slower:

class Package
{
public List<GroupList> groupList;

public override bool Equals(object o)
{
Package p = o as Package;
if (p == null) return false;
return !groupList.Except(p.groupList).Any();
}
}

class GroupList
{
public List<Feature> featureList;

public override bool Equals(object o)
{
GroupList g = o as GroupList;
if (g == null) return false;
return !featureList.Except(f.featureList).Any();
}
}

Comparing two enumrations of PropertyInfo that results from reflection

When you compare two Objects using the == (or !=) operators you're actually using the System.Object.Equals() method, and you almost certainly don't like the implementation, that's why you're saying it doesn't work. Unless the types you're using have overriden this to provide a meaningful compassion, you get the framework provided implementation and that actually tests for identity, not meaningful equality.

If your properties were of simple types that do provide proper equality behavior (String, Int32, etc) then your code would work. Since it does't, your types do not implement System.Object.Equals() in a way you consider meaningful. What you probably want is a form of "deep instance comparison", where the two objects are considered equal if they're the same type and each corresponding field is also equal. Well, you don't get that!

The solution depends on the Types of your properties. If you implemented then consider implementing Equals() yourself. If you didn't implement them then you'll need to find an other way.

Comparing two complex objects

What you are looking for is called a deep or recursive comparison. Unfortunately, there's nothing built-in in the .NET framework to do that.

This is a non-trivial task, especially if you have nested collection types. The following question list some common solutions which the C# folks have found for this problem. They might be an option for you as well, if you convert them to VB or use them as an external library:

  • C# implementation of deep/recursive object comparison in .net 3.5


Related Topics



Leave a reply



Submit