преобразование .net Func <T> в .net Expression <Func <T>>


118

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

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


Я не вижу особого смысла в примере «редкий случай». Вызывающий передает Func <T>. Нет необходимости повторять вызывающему объекту, что это был за Func <T> (через исключение).
— Адам Ральф

2
Исключение не обрабатывается вызывающей стороной. И поскольку существует несколько сайтов вызова, переданных в разные Func <T> s, перехват исключения в вызывающей стороне создает дублирование.
— Дэйв Кэмерон,

1
Трассировка стека исключений предназначена для отображения этой информации. Если исключение выбрасывается при вызове Func <T>, это будет отображаться в трассировке стека. Между прочим, если бы вы выбрали другой путь, т.е. приняли выражение и скомпилировали его для вызова, вы бы потеряли это, поскольку трассировка стека показала бы что-то вроде at lambda_method(Closure )вызова скомпилированного делегата.
— Адам Ральф

Думаю, вам стоит посмотреть ответ в этой [ссылка] [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/…
— Ибрагим Кайс Ибрагим

Ответы:


104

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

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

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


3
Лисп не нужно интерпретировать, его легко скомпилировать. Макросы должны быть расширены во время компиляции, и если вы хотите поддержать, evalвам нужно будет запустить компилятор, но в остальном нет никаких проблем с этим.
— конфигуратор

2
"Выражение <Func <T>> DangerousExpression = () => dangerousCall ();" нелегко?
— mheyman

10
@mheyman Это создаст новую информацию Expressionо вашем действии оболочки, но не будет иметь никакой информации о дереве выражений о внутренних компонентах dangerousCallделегата.
— Nenad

34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 

1
Я хотел пройти по синтаксическому дереву возвращенного выражения. Позволит ли мне такой подход сделать это?
— Дэйв Кэмерон

6
@DaveCameron - Нет. См. Ответы выше - уже скомпилированные Funcбудут скрыты в новом выражении. Это просто добавляет один уровень данных поверх кода; вы можете пройти по одному слою, чтобы найти свой параметр fбез дополнительной информации, так что вы там, где начали.
— Jonno 01

21

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

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


7

Однако вы можете пойти другим путем с помощью метода .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());
}

6

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

  • иметь разные методы (по 1 на каждый)
  • всегда принимайте Expression<...>версию, и просто .Compile().Invoke(...)ее, если вам нужен делегат. Очевидно, это дорого.

6

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
 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);
        }

Не могли бы вы уточнить часть «это не сработает»? Вы действительно пробовали его скомпилировать и запустить? Или в вашем приложении это особо не работает?
— Дмитрий Дзыгин

1
FWIW, возможно, главный тикет был не в этом, но мне это было нужно. Это была call.Targetчасть, которая меня убивала. Он работал годами, а потом внезапно перестал работать и начал жаловаться на статический / нестатический бла-бла. В любом случае, спасибо!
— Эли Гассерт


-1

+ Изменить

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

к

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

Серви, это как абсолютно законный способ самовыражения. синтаксический сахар, чтобы построить его через expression.lambda и expression.call. Как вы думаете, почему он должен давать сбой во время выполнения?
— Роман Покровский
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.