c# - funciones - Establezca el operador de comparación de forma dinámica utilizando la expresión lambda
funciones anonimas c# (2)
Para establecer dinámicamente un operador de comparación en la consulta linq, hago lo siguiente:
parameter = Expression.Parameter(typeof(SomeType));
var predicate = Expression.Lambda<Func<SomeType, bool>>(
Combine(
"=",
Expression.Property(parameter, "ID"),
Expression.Constant(150497)
), parameter);
BinaryExpression Combine(string op, Expression left, Expression right)
{
switch (op)
{
case "=":
return Expression.Equal(left, right);
case "<":
return Expression.LessThan(left, right);
case ">":
return Expression.GreaterThan(left, right);
}
return null;
}
Eso funciona. Pero preferiría pasar una expresión lambda como parámetro "izquierda" en su lugar. ¿Es eso posible? Algo como:
var predicate = Expression.Lambda<Func<SomeType, bool>>(Combine(
"=",
c => c.ID,
Expression.Constant(150497)
), parameter);
¿Qué hay de esto? Lamentablemente, no puedo probarlo ahora, así que avíseme si no funciona
private class TestCalss
{
public int Id { get; set; }
}
private class SwapVisitor : ExpressionVisitor
{
public readonly Expression _from;
public readonly Expression _to;
public SwapVisitor(Expression from, Expression to)
{
_from = from;
_to = to;
}
public override Expression Visit(Expression node) => node == _from ? _to : base.Visit(node);
}
private BinaryExpression Combine<T, TResult>(string op, Expression<Func<T, TResult>> left, Expression right, ParameterExpression parameter)
{
// Need to use parameter from outer lambda expression for equality two expressions
var swap = new SwapVisitor(left.Parameters[0], parameter);
var newLeft = swap.Visit(left) as Expression<Func<T, TResult>>;
switch (op)
{
case "=":
return Expression.Equal(newLeft.Body, right);
case "<":
return Expression.LessThan(newLeft.Body, right);
case ">":
return Expression.GreaterThan(newLeft.Body, right);
}
return null;
}
...
var parameter = Expression.Parameter(typeof(TestCalss));
var predicate = Expression.Lambda<Func<TestCalss, bool>>(
Combine<TestCalss, int>("=", c => c.Id, Expression.Constant(156), parameter),
parameter);
var test = new TestCalss { Id = 156 };
var result = predicate.Compile()(test); // <- true
Básicamente, desea acceder a los campos de una clase sin utilizar cadenas, y eso es posible si sus campos son públicos.
Aquí puedes ver un buen ejemplo de cómo se hace eso.
En cuanto a su uso específico de la misma, sería algo así como:
public class Test
{
public static void someMethod()
{
var parameter = Expression.Parameter(typeof(SomeType));
var predicate = Expression.Lambda<Func<SomeType, bool>>(Combine(
"=",
Expression.Parameter(typeof(int), GetMemberName((SomeType c) => c.ID)),
Expression.Constant(150497)
), parameter);
}
public static BinaryExpression Combine(string op, Expression left, Expression right)
{
switch (op)
{
case "=":
return Expression.Equal(left, right);
case "<":
return Expression.LessThan(left, right);
case ">":
return Expression.GreaterThan(left, right);
}
return null;
}
public static string GetMemberName<T, TValue>(Expression<Func<T, TValue>> memberAccess)
{
return ((MemberExpression)memberAccess.Body).Member.Name;
}
}
public class SomeType
{
public int ID { get; set; }
private string aString;
}
Descargo de responsabilidad : No lo probé, pero la lógica está ahí.
Como se mencionó, no podría acceder a SomeType.aString
porque es private
. También puse typeof(int)
pero si quieres que incluso eso sea dinámico, podrías tener otro método (es decir, GetMemberType
) para obtener el tipo del campo.