c# - sentencias - Obtener el valor del parámetro de una expresión Linq
no se puede convertir expresion lambda en el tipo delegado (3)
Tengo la siguiente clase
public class MyClass
{
public bool Delete(Product product)
{
// some code.
}
}
Ahora tengo una clase de ayuda que se ve así
public class Helper<T, TResult>
{
public Type Type;
public string Method;
public Type[] ArgTypes;
public object[] ArgValues;
public Helper(Expression<Func<T, TResult>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
this.Type = typeof(T);
this.Method = body.Method.Name;
this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
this.ArgValues = ???
}
}
La idea es usar este código desde algún lugar:
// I am returning a helper somewhere
public Helper<T> GetMethod<T>()
{
var product = GetProduct(1);
return new Helper<MyClass>(x => x.Delete(product));
}
// some other class decides, when to execute the helper
// Invoker already exists and is responsible for executing the method
// that is the main reason I don''t just comile and execute my Expression
public bool ExecuteMethod<T>(Helper<T> helper)
{
var instance = new MyClass();
var Invoker = new Invoker(helper.Type, helper.Method, helper.ArgTypes, helper.ArgValues);
return (bool)Invoker.Invoke(instance);
}
El punto en el que estoy atascado es cómo extraer los argumentos de la expresión en sí.
Me encontré de esta manera
((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value
que parece ser un tipo de objeto con un campo "producto" pero creo que debe haber una solución más simple.
Alguna sugerencia.
Actualizar
Solo para aclarar, modifiqué mi código de acuerdo con lo que quiero lograr. En mi aplicación de palabra real ya tengo una clase que hace lo mismo pero sin un árbol de expresiones:
var helper = new Helper(typeof(MyClass), "Delete",
new Type[] { typeof(Product) }, new object[] {product}));
La razón principal de mi Helper<T>
es tener una verificación en tiempo de compilación si la firma del método es válida.
Actualización 2
Esta es mi implementación actual, ¿hay una mejor manera de acceder a los valores sin utilizar la reflexión?
public Helper(Expression<Func<T, TResult>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
this.Type = typeof(T);
this.Method = body.Method.Name;
this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
var values = new List<object>();
foreach(var arg in body.Arguments)
{
values.Add(
(((ConstantExpression)exp.Expression).Value).GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
);
}
this.ArgValues = values.ToArray();
}
Aquí hay un ejemplo de la creación de un delegado usando una lambda. La instancia de objeto se encapsula en el delegado mediante una función de C # llamada cierre.
MyClass instance = new MyClass();
//This following line cannot be changed to var declaration
//since C# can''t infer the type.
Func<Product, bool> deleteDelegate = p => instance.Delete(p);
Product product = new Product();
bool deleted = deleteDelegate(product);
Alternativamente, usted está tratando de crear un Ayudante que Automáticamente Currys.
public class Helper<T>
where T : new()
{
public TResult Execute<TResult>(Func<T, TResult> methodLambda)
{
var instance = new T();
return methodLamda(instance);
}
}
public void Main()
{
var helper = new Helper<MyClass>();
var product = new Product();
helper.Execute(x => x.Delete(product));
}
Sin embargo, tengo que decir que este problema se parece sospechosamente a la creación de una clase de Ayudante para manejar la vida útil de un proxy de WCF ... Ya sabes ... solo digo ... en cuyo caso esto NO ES cómo abordaría esto ... simplemente porque este enfoque filtra código específico de WCF en su dominio.
Este método funciona bastante bien. Devuelve los tipos de argumento y valores para una expresión>
private static KeyValuePair<Type, object>[] ResolveArgs<T>(Expression<Func<T, object>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
var values = new List<KeyValuePair<Type, object>>();
foreach (var argument in body.Arguments)
{
var exp = ResolveMemberExpression(argument);
var type = argument.Type;
var value = GetValue(exp);
values.Add(new KeyValuePair<Type, object>(type, value));
}
return values.ToArray();
}
public static MemberExpression ResolveMemberExpression(Expression expression)
{
if (expression is MemberExpression)
{
return (MemberExpression)expression;
}
else if (expression is UnaryExpression)
{
// if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname)
return (MemberExpression)((UnaryExpression)expression).Operand;
}
else
{
throw new NotSupportedException(expression.ToString());
}
}
private static object GetValue(MemberExpression exp)
{
// expression is ConstantExpression or FieldExpression
if (exp.Expression is ConstantExpression)
{
return (((ConstantExpression)exp.Expression).Value)
.GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
}
else if (exp.Expression is MemberExpression)
{
return GetValue((MemberExpression)exp.Expression);
}
else
{
throw new NotImplementedException();
}
}
Puede compilar la expresión de argumento y luego invocarla para calcular el valor:
var values = new List<object>();
foreach(var arg in body.Arguments)
{
var value = Expression.Lambda(argument).Compile().DynamicInvoke();
values.Add(value);
}
this.ArgValues = values.ToArray();