преобразование .net Func <T>в выражение .net <Func <T>> - PullRequest
109 голосов
/ 20 апреля 2009

Переход от лямбды к выражению легко с помощью вызова метода ...

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}

Но я бы хотел превратить Func в выражение, только в редких случаях ...

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>>'. Явное приведение не разрешает ситуацию. Есть ли возможность сделать это, что я пропускаю?

Ответы [ 9 ]

95 голосов
/ 20 апреля 2009

Ох, это совсем не просто. Func<T> представляет собой общий delegate, а не выражение. Если есть какой-либо способ сделать это (из-за оптимизаций и других действий, выполняемых компилятором, некоторые данные могут быть выброшены, поэтому может оказаться невозможным вернуть исходное выражение), это будет разборка IL на лету и вывод выражения (что ни в коем случае не легко). Обработка лямбда-выражений как данных (Expression<Func<T>>) - это волшебство, совершаемое компилятором (в основном компилятор строит дерево выражений в коде, а не компилирует его в IL).

Связанный факт

Вот почему языки, которые толкают лямбды до крайности (например, Лисп), часто проще реализовать как интерпретаторы . В этих языках код и данные по сути одно и то же (даже в время выполнения ), но наш чип не может понять эту форму кода, поэтому мы должны эмулировать такую ​​машину, создавая интерпретатор поверх тот, кто понимает это (выбор, сделанный Lisp-подобными языками) или жертвуя силой (код больше не будет точно равен данным) в некоторой степени (выбор, сделанный C #). В C # компилятор создает иллюзию обработки кода как данных, позволяя интерпретировать лямбды как code (Func<T>) и data (Expression<Func<T>>) при compile время .

28 голосов
/ 27 февраля 2011
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 
21 голосов
/ 20 апреля 2009

Что вы, вероятно, должны сделать, это перевернуть метод. Возьмите 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());
}

Очевидно, что вам необходимо учитывать последствия этого для производительности и определить, действительно ли это то, что вам действительно нужно сделать.

6 голосов
/ 20 апреля 2009

Однако вы можете пойти другим путем с помощью метода .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 голосов
/ 21 марта 2013
 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);
        }
5 голосов
/ 02 сентября 2012

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)
    } 
}
4 голосов
/ 01 мая 2009

JB Evain из команды Cecil Mono делает некоторый прогресс для включения этого

http://evain.net/blog/articles/2009/04/22/converting-delegates-to-expression-trees

4 голосов
/ 20 апреля 2009

Если вам иногда нужно выражение, а иногда делегат, у вас есть 2 варианта:

  • имеют разные методы (по 1 для каждого)
  • всегда принимайте версию Expression<...> и просто .Compile().Invoke(...), если хотите делегата. Очевидно, что это стоило.
0 голосов
/ 18 февраля 2014

Изменение

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

Для

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();
...