Преобразование .net Func <T> в .net Expression <Func <T>>
Переход от лямбды к выражению легко с помощью вызова метода...
public void GimmeExpression(Expression<Func<T>> expression)
{
((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}
public void SomewhereElse()
{
GimmeExpression(() => thing.DoStuff());
}
Но я хотел бы включить Func in в выражение, только в редких случаях...
public void ContainTheDanger(Func<T> dangerousCall)
{
try
{
dangerousCall();
}
catch (Exception e)
{
// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;
var nameOfDanger =
((MemberExpression)dangerousCall.Body).Member.Name;
throw new DangerContainer(
"Danger manifested while " + nameOfDanger, e);
}
}
public void SomewhereElse()
{
ContainTheDanger(() => thing.CrossTheStreams());
}
Строка, которая не работает, дает мне ошибку времени компиляции Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'
. Явное приведение не разрешает ситуацию. Есть ли возможность сделать это, что я не замечаю?
Ответы
Ответ 1
Ой, это совсем не просто. Func<T>
представляет общий delegate
, а не выражение. Если вы каким-либо образом можете это сделать (из-за оптимизации и других действий, выполняемых компилятором, некоторые данные могут быть выброшены, поэтому было бы невозможно вернуть исходное выражение), он будет разбирать IL на лету и вывести выражение (что отнюдь не просто). Обработка лямбда-выражений как данных (Expression<Func<T>>
) - это волшебство, выполняемое компилятором (в основном компилятор строит дерево выражений в коде вместо компиляции его в IL).
Связанный факт
Вот почему языки, которые нажимают лямбды до крайности (например, Lisp), часто проще реализовать как интерпретаторы. В этих языках код и данные по существу являются одними и теми же (даже во время выполнения), но наш чип не может понять эту форму кода, поэтому мы должны подражать такой машине, построив на ней интерпретатор, который ее понимает ( выбор, сделанный Lisp подобными языками) или жертвующий силой (код больше не будет точно равен данным) в некоторой степени (выбор сделан С#). В С# компилятор дает иллюзию обработки кода как данных, позволяя lambdas интерпретироваться как код (Func<T>
) и datastrong > (Expression<Func<T>>
) во время компиляции.
Ответ 2
private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)
{
return x => f(x);
}
Ответ 3
То, что вы, вероятно, должны сделать, - это включить метод. Возьмите Expression > , и скомпилируйте и запустите. Если он терпит неудачу, у вас уже есть выражение, чтобы посмотреть.
public void ContainTheDanger(Expression<Func<T>> dangerousCall)
{
try
{
dangerousCall().Compile().Invoke();;
}
catch (Exception e)
{
// This next line does not work...
var nameOfDanger =
((MemberExpression)dangerousCall.Body).Member.Name;
throw new DangerContainer(
"Danger manifested while " + nameOfDanger, e);
}
}
public void SomewhereElse()
{
ContainTheDanger(() => thing.CrossTheStreams());
}
Очевидно, вам нужно учитывать последствия этого для производительности и определить, действительно ли это нужно сделать.
Ответ 4
Вы можете пойти другим способом с помощью метода .Compile(), однако - не уверен, что это полезно для вас:
public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
{
try
{
var expr = dangerousCall.Compile();
expr.Invoke();
}
catch (Exception e)
{
Expression<Func<T>> DangerousExpression = dangerousCall;
var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
}
}
public void SomewhereElse()
{
var thing = new Thing();
ContainTheDanger(() => thing.CrossTheStreams());
}
Ответ 5
Expression<Func<T>> ToExpression<T>(Func<T> call)
{
MethodCallExpression methodCall = call.Target == null
? Expression.Call(call.Method)
: Expression.Call(Expression.Constant(call.Target), call.Method);
return Expression.Lambda<Func<T>>(methodCall);
}
Ответ 6
Если вам иногда требуется выражение, а иногда требуется делегат, у вас есть 2 варианта:
- имеют разные методы (по 1 для каждого)
- всегда принимайте версию
Expression<...>
и просто .Compile().Invoke(...)
, если вы хотите делегировать. Очевидно, что это стоило.
Ответ 7
JB Evain из команды Cecil Mono делает некоторый прогресс, чтобы включить этот
http://evain.net/blog/articles/2009/04/22/converting-delegates-to-expression-trees
Ответ 8
NJection.LambdaConverter - это библиотека, которая преобразует делегаты в выражение
public class Program
{
private static void Main(string[] args) {
var lambda = Lambda.TransformMethodTo<Func<string, int>>()
.From(() => Parse)
.ToLambda();
}
public static int Parse(string value) {
return int.Parse(value)
}
}
Ответ 9
Много полезного можно найти здесь
Ответ 10
Изменить
// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;
Для
// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();