Al crear mi marco de prueba, he encontrado un problema extraño.Comparar PropertyInfo de Type.GetProperties() y expresiones lambda
Quiero crear una clase estática que me permita comparar objetos del mismo tipo por sus propiedades, pero con la posibilidad de ignorar algunos de ellos.
Quiero tener un simple API fluida para esto, así como una llamada TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second);
devolverá verdadero si los objetos dados son iguales en todos los bienes, excepto Id
y Name
(que no serán comprobados por la igualdad).
Aquí va mi código. Por supuesto, es un ejemplo trivial (con algunas obvias sobrecargas de métodos faltantes), pero quería extraer el código más simple posible. El escenario real es un poco más complejo, así que realmente no quiero cambiar el enfoque.
El método FindProperty
es casi un copiar y pegar de AutoMapper library.
envoltorio objeto de API fluida:
public class TestEqualityHelper<T>
{
public List<PropertyInfo> IgnoredProps = new List<PropertyInfo>();
public T Value;
}
cosas Fluido:
public static class FluentExtension
{
//Extension method to speak fluently. It finds the property mentioned
// in 'ignore' parameter and adds it to the list.
public static TestEqualityHelper<T> Ignore<T>(this T value,
Expression<Func<T, object>> ignore)
{
var eh = new TestEqualityHelper<T> { Value = value };
//Mind the magic here!
var member = FindProperty(ignore);
eh.IgnoredProps.Add((PropertyInfo)member);
return eh;
}
//Extract the MemberInfo from the given lambda
private static MemberInfo FindProperty(LambdaExpression lambdaExpression)
{
Expression expressionToCheck = lambdaExpression;
var done = false;
while (!done)
{
switch (expressionToCheck.NodeType)
{
case ExpressionType.Convert:
expressionToCheck
= ((UnaryExpression)expressionToCheck).Operand;
break;
case ExpressionType.Lambda:
expressionToCheck
= ((LambdaExpression)expressionToCheck).Body;
break;
case ExpressionType.MemberAccess:
var memberExpression
= (MemberExpression)expressionToCheck;
if (memberExpression.Expression.NodeType
!= ExpressionType.Parameter &&
memberExpression.Expression.NodeType
!= ExpressionType.Convert)
{
throw new Exception("Something went wrong");
}
return memberExpression.Member;
default:
done = true;
break;
}
}
throw new Exception("Something went wrong");
}
}
El comparador real:
public static class TestEqualityComparer
{
public static bool MyEquals<T>(TestEqualityHelper<T> a, T b)
{
return DoMyEquals(a.Value, b, a.IgnoredProps);
}
private static bool DoMyEquals<T>(T a, T b,
IEnumerable<PropertyInfo> ignoredProperties)
{
var t = typeof(T);
IEnumerable<PropertyInfo> props;
if (ignoredProperties != null && ignoredProperties.Any())
{
//THE PROBLEM IS HERE!
props =
t.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Except(ignoredProperties);
}
else
{
props =
t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
}
return props.All(f => f.GetValue(a, null).Equals(f.GetValue(b, null)));
}
}
Eso es básicamente la misma.
Y aquí hay dos fragmentos de prueba, la primera de ellas funciona, la segunda falla:
//These are the simple objects we'll compare
public class Base
{
public decimal Id { get; set; }
public string Name { get; set; }
}
public class Derived : Base
{ }
[TestMethod]
public void ListUsers()
{
//TRUE
var f = new Base { Id = 5, Name = "asdas" };
var s = new Base { Id = 6, Name = "asdas" };
Assert.IsTrue(TestEqualityComparer.MyEquals(f.Ignore(x => x.Id), s));
//FALSE
var f2 = new Derived { Id = 5, Name = "asdas" };
var s2 = new Derived { Id = 6, Name = "asdas" };
Assert.IsTrue(TestEqualityComparer.MyEquals(f2.Ignore(x => x.Id), s2));
}
El problema es con el método Except
en DoMyEquals
.
Las propiedades devueltas por FindProperty
no son iguales a las devueltas por Type.GetProperties
. La diferencia que veo es en PropertyInfo.ReflectedType
.
sin tener en cuenta el tipo de mis objetos,
FindProperty
me dice que el tipo reflejada esBase
.propiedades devueltos por
Type.GetProperties
tienen su conjuntoReflectedType
-Base
oDerived
, en función del tipo de objetos reales.
no sé cómo resolverlo. Pude comprobar el tipo de parámetro en lambda, pero en el siguiente paso quiero permitir construcciones como Ignore(x=>x.Some.Deep.Property)
, por lo que probablemente no funcionará.
Cualquier sugerencia sobre cómo comparar PropertyInfo
o cómo recuperarlos de lambdas correctamente sería apreciada.
¿Ha intentado jugar con el valor BindingFlags.FlattenHierarchy en GetProperties? ¿Ves si cambia algo? –
No hubo suerte, pero gracias por una sugerencia. Creo ** que BindingFlags solo puede cambiar _que _se devuelven miembros, pero no afectarán sus propias propiedades. Creo que la solución estaría en algún juego con FindProperty. –
Quizás agregando un segundo paso hacky a FindProperty, después de obtener el miembro, ejecute GetProperty en el tipo (que también puede obtener a través de la expresión) con el nombre del miembro. Es un truco, pero podría funcionar. –