Получить значение параметра из выражения Linq
У меня есть следующий класс
public class MyClass
{
public bool Delete(Product product)
{
// some code.
}
}
Теперь у меня есть вспомогательный класс, который выглядит как
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 = ???
}
}
Идея использовать этот код где-то:
// 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);
}
Точка, в которой я застреваю, - это извлечение аргументов из самого выражения.
Я нашел этот путь
((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value
который, как представляется, является типом объекта с полевым "продуктом", но я считаю, что должно быть более простое решение.
Любые предложения.
Обновление
Чтобы уточнить, я изменил свой код в соответствии с тем, что хочу достичь. В моем реальном приложении слова у меня уже есть класс, который делает то же самое, но без дерева выражений:
var helper = new Helper(typeof(MyClass), "Delete",
new Type[] { typeof(Product) }, new object[] {product}));
Основной причиной моего Helper<T>
является проверка времени компиляции, если подпись метода действительна.
Обновление 2
Это моя текущая реализация, есть ли лучший способ получить значения без использования рефлексии?
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();
}
Ответы
Ответ 1
Этот метод работает очень хорошо. Он возвращает типы и значения аргументов выражения Expression >
private static KeyValuePair<Type, object>[] ResolveArgs(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();
}
}
Ответ 2
Вы можете скомпилировать выражение аргумента, а затем вызвать его для вычисления значения:
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();
Ответ 3
Вот пример создания делегата с использованием лямбда. Экземпляр объекта инкапсулируется в делегат, используя функцию С#, называемую замыканием.
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);
В качестве альтернативы вы пытаетесь создать помощника, автоматически создающего 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));
}
Однако я должен сказать, что эта проблема выглядит подозрительно, как создание класса Helper для обработки жизненного цикла прокси-сервера WCF.... Вы знаете... просто скажите... в этом случае это НЕ, как я будет подходить к этому... просто потому, что этот подход утечки специфического кода WCF в ваш домен.