Сравнение всех свойств объекта с использованием деревьев выражений
Я пытаюсь написать простой генератор, который использует дерево выражений для динамической генерации метода, который сравнивает все свойства экземпляра типа со свойствами другого экземпляра этого типа. Это прекрасно работает для большинства свойств, таких как int
string
, но не для DateTime?
(и, вероятно, другие типы значений, допускающие значение NULL).
Метод:
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;
}
На DateTime?
свойство это терпит неудачу с:
Выражение типа 'System.Nullable`1[System.DateTime]' нельзя использовать для параметра типа 'System.Object' метода 'Boolean Equals(System.Object)'
ОК, так что он находит перегрузку Equals
что ожидает object
, Так почему я не могу передать DateTime?
в это, так как он конвертируется в object
? Если я смотрю на Nullable<T>
это действительно имеет переопределение Equals(object o)
,
PS: я понимаю, что это еще не настоящий генератор, поскольку он не может справиться с null
значения, но я доберусь до этого:)
ОБНОВЛЕНИЕ: ответ Ираклиса сработал для этой конкретной проблемы, но в конце концов я выбрал гораздо более простой подход, который, на мой взгляд, достаточно: просто используйте Expression.Equal
, Я думаю, что это покрывает 99% моих случаев (не уверен, что справится с переопределением Equals
без переопределения ==
, но это нормально).
2 ответа
Это может сработать, если вы проверите, что типы обнуляются с помощью этого кода:
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)){}
Пример кода отсюда
И если они Nullable, то вы можете позвонить
Nullable.Equals<T>(T? n1, T? n2);
После поиска в Интернете чего-то, что я могу использовать, я решил реализовать это и сам. Я не использовал деревья выражений. Вместо этого я использую отражение, чтобы просмотреть все свойства и использовать ToString()
сравнить их. Если свойство является коллекцией, оно будет сравнивать каждый элемент в коллекции на равенство.
Вот код;
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();
}
}
}
Если свойство класса само является классом, то оно создаст новый компаратор, который может сравнивать свойства этого класса. Вы также можете при желании отметить определенные свойства, которые будут исключены из сравнения, используя EqualityIrrelevantAttribute
, Это работает очень хорошо для меня, я надеюсь, что другие найдут это полезным.