Преобразование .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>) и data​​strong > (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(...), если вы хотите делегировать. Очевидно, что это стоило.

Ответ 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();