Comparing all properties of an object using expression trees
-
08-10-2019 - |
Question
I'm trying to write a simple generator that uses an expression tree to dynamically generate a method that compares all properties of an instance of a type to the properties of another instance of that type. This works fine for most properties, like int
an string
, but fails for DateTime?
(and presumably other nullable value types).
The method:
static Delegate GenerateComparer(Type type)
{
var left = Expression.Parameter(type, "left");
var right = Expression.Parameter(type, "right");
Expression result = null;
foreach (var p in type.GetProperties())
{
var leftProperty = Expression.Property(left, p.Name);
var rightProperty = Expression.Property(right, p.Name);
var equals = p.PropertyType.GetMethod("Equals", new[] { p.PropertyType });
var callEqualsOnLeft = Expression.Call(leftProperty, equals, rightProperty);
result = result != null ? (Expression)Expression.And(result, callEqualsOnLeft) : (Expression)callEqualsOnLeft;
}
var method = Expression.Lambda(result, left, right).Compile();
return method;
}
On a DateTime?
property it fails with:
Expression of type 'System.Nullable`1[System.DateTime]' cannot be used for parameter of type 'System.Object' of method 'Boolean Equals(System.Object)'
OK, so it finds an overload of Equals
that expects object
. So why can't I pass a DateTime?
into that, as it's convertible to object
? If I look at Nullable<T>
, it indeed has an override of Equals(object o)
.
PS: I realize that this isn't a proper generator yet as it can't deal with null
values, but I'll get to that :)
UPDATE: Iraklis' answer did work for this particular problem, but in the end I went for a much simpler approach that I think suffices: just use Expression.Equal
. I think that covers 99% of my cases (not sure if it can handle overriding Equals
without overriding ==
, but that's OK).
Solution
it might work if you check that the types are nullable with this code:
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)){}
The code sample is from here
And if they are Nullable then you can call
Nullable.Equals<T>(T? n1, T? n2);
OTHER TIPS
After searching the web for something I can use, I decided to implement it myself as well. I did not use expression trees. Instead I use reflection to scan all the properties and use ToString()
to compare them. If the property is a collection, it will compare every element in the collection for equality.
Here is the code;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
namespace Utils
{
public class PropertyComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
IEnumerable<PropertyInfo> allProperties = typeof(T).GetProperties();
foreach(PropertyInfo pi in allProperties)
{
if (pi.GetCustomAttributes<EqualityIrrelevantAttribute>().Any())
{
continue;
}
object xProp = pi.GetValue(x);
object yProp = pi.GetValue(y);
if ((xProp == null) && (yProp == null))
{
continue;
}
else if ((xProp == null) || (yProp == null))
{
return false;
}
else if (xProp is ICollection)
{
if (!CollectionsEqual(xProp as ICollection, yProp as ICollection))
{
return false;
}
}
if (xProp.ToString() != yProp.ToString())
{
return false;
}
}
return true;
}
bool CollectionsEqual(ICollection left, ICollection right)
{
IEnumerator leftEnumerator = left.GetEnumerator();
IEnumerator rightEnumerator = right.GetEnumerator();
bool leftAdvanced = leftEnumerator.MoveNext();
bool rightAdvanced = rightEnumerator.MoveNext();
if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced))
{
return false;
}
else if (!leftAdvanced && !rightAdvanced)
{
return true;
}
bool compareByClass = false;
object comparer = null;
MethodInfo equalsMethod = null;
// Inspect type first
object peek = leftEnumerator.Current;
Type valuesType = peek.GetType();
if (valuesType.IsClass)
{
compareByClass = true;
Type comparerType = typeof(PropertyComparer<>).MakeGenericType(new Type[] { valuesType });
equalsMethod = comparerType.GetMethod("Equals", new Type[] { valuesType, valuesType });
comparer = Activator.CreateInstance(comparerType);
}
leftEnumerator.Reset();
rightEnumerator.Reset();
while (true)
{
leftAdvanced = leftEnumerator.MoveNext();
rightAdvanced = rightEnumerator.MoveNext();
if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced))
{
return false;
}
else if (!leftAdvanced && !rightAdvanced)
{
return true;
}
object leftValue = leftEnumerator.Current;
object rightValue = rightEnumerator.Current;
if (compareByClass)
{
bool result = (bool)equalsMethod.Invoke(comparer, new object[] { leftValue, rightValue });
if (!result)
{
return false;
}
}
else if (leftEnumerator.Current.ToString() != rightEnumerator.Current.ToString())
{
return false;
}
// Continue looping
}
}
public int GetHashCode(T obj)
{
throw new NotImplementedException();
}
}
}
If a property of a class is itself a class, then it will create a new comparer that can compare properties of that class. You can also optionally mark particular properties to be excluded from comparision using the EqualityIrrelevantAttribute
. It works really well for me, I hope others find it useful.