Reputation: 17156
I have the following method to compare DTOs.
bool Equals<T1, T2>(T1 t1, T2 t2, params Expression<Func<T1, object>>[] accessors)
{
return !(
from accessor in accessors
select ((MemberExpression) accessor.Body).Member.Name into propertyName
let p1 = typeof (T1).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
let p2 = typeof (T2).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
let p1val = p1.GetValue(t1, null)
let p2val = p2.GetValue(t2, null)
where !Equals(p1val, p2val)
select p1val
).Any();
}
I can call this using (a
and b
are instances of objects that by convention share the same properties, but that aren't the same objects):
Equals(a, b, x => x.PropertyOne, x => x.PropertyTwo );
Which compares the the objects property by property, which is fine for most cases.
However, I found a case where I needed to compare objects that had properties of complex types and where I wanted to compare properties on the complex types instead of the objects. Something like this:
Equals(a, b, x => x.ComplexTypeProperty.ChildProp );
I have realised that I need to leave the comfy reflection comparison and enter the Expression land, but the main task here is to be able to express both a property accessor and a property accessor via a complex type property and that's where I'm lost.
Any pointers would be nice, thanks!
Upvotes: 0
Views: 641
Reputation: 12619
The task is not so complicated:
Determine property path or expressions that are given by expressions. For instance this extension method will give you this:
public static IEnumerable<string> GetPropertiesNames<T, G>(this Expression<Func<T, G>> pathExpression)
{
List<string> _propertyNames = new List<string>();
Expression expression = pathExpression.Body;
if (expression.NodeType == ExpressionType.Convert)
{
var convert = (UnaryExpression)pathExpression.Body;
expression = convert.Operand;
}
while (expression.NodeType == ExpressionType.MemberAccess)
{
MemberExpression memberExpression = (MemberExpression)expression;
if(!(memberExpression.Member is PropertyInfo))
throw new InvalidOperationException();
_propertyNames.Add(memberExpression.Member.Name);
expression = memberExpression.Expression;
}
if (expression.NodeType != ExpressionType.Parameter)
throw new InvalidOperationException();
return _propertyNames;
}
Aggregate expression for second type to create function that will return value:
var parameter = Expression.Parameter(typeof(T2));
var expressionToConvert = accessors[0]; //for future loop
var propertyChainDescriptor = expressionToConvert.GetPropertiesNames()
.Aggregate(new { Expression = (Expression)parameterCasted, Type = typeof(T2)},
(current, propertyName) =>
{
var property = current.Type.GetProperty(propertyName);
var expression = Expression.Property(current.Expression, property);
return new { Expression = (Expression)expression, Type = property.PropertyType };
});
var body = propertyChainDescriptor.Expression;
if (propertyChainDescriptor.Type.IsValueType)
{
body = Expression.Convert(body, typeof(object));
}
var t2PropertyRetriver = Expression.Lambda<Func<T2, object>>(body, parameter).Compile();
Now execute method that retrieve values and compare:
var t1PropertyRetriver = accessor[0].Compile();
var t1Value = t1PropertyRetriver(t1);
var t2Value = t2PropertyRetriver(t2);
var areEqual = object.Equals(t1Value,t2Value);
The good idea would be to add some caching of generated methods because compilation process is expensive.
Upvotes: 1