перетворення .net Func <T> в .net Вираз <Func <T>>


118

Перехід від лямбда до Expression легко за допомогою виклику методу ...

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

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

Але я хотів би перетворити функцію на вираз, лише в рідкісних випадках ...

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>>'. Явний виступ не вирішує ситуацію. Чи є можливість зробити це, що я оглядаю?


Я не бачу великої користі для прикладу «рідкісного випадку». Абонент передає функцію <T>. Немає необхідності повторювати назад абоненту, що це за Func <T> (за винятком).
Адам Ральф

2
Виняток не обробляється у абонента. Оскільки в різних функціях <T> s проходить кілька сайтів викликів, виловлення виключення у абонента створює дублювання.
Дейв Кемерон

1
Трасування стека винятків призначене для відображення цієї інформації. Якщо виняток буде викинуто в межах виклику Func <T>, це відобразиться у трасі стека. Між іншим, якби ви вирішили піти іншим шляхом, тобто прийняти вираз і скласти його для виклику, ви втратили б це, оскільки слід стека показав би щось на зразок at lambda_method(Closure )виклику компільованого делегата.
Адам Ральф

Я думаю, ви повинні подивитися на відповідь у цьому [посилання] [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/…
Ібрагім Каїс Ібрагім

Відповіді:


104

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

Супутній факт

Ось чому мови, які підштовхують лямбда до крайності (як, наприклад, Лісп), часто легше реалізувати як перекладачів . У цих мовах код і дані - це по суті те саме (навіть під час виконання ), але наш чіп не може зрозуміти цю форму коду, тому нам доведеться емулювати таку машину, будуючи інтерпретатор, що розуміє її ( вибір, зроблений Lisp як мови) або жертвуючи потужністю (код більше не буде точно рівним даним) певною мірою (вибір зроблений C #). У C # компілятор подає ілюзію трактування коду як даних, дозволяючи лямбдам інтерпретувати як код ( Func<T>) та дані ( Expression<Func<T>>) під час компіляції .


3
Lisp не потрібно інтерпретувати, його можна легко скласти. Макроси потрібно було б розширити під час компіляції, і якщо ви хочете підтримати, evalвам знадобиться запустити компілятор, але крім цього, це взагалі не виникає.
конфігуратор

2
"Вираз <Func <T>> DangerousExpression = () => небезпечний виклик ();" непросто?
mheyman

10
@mheyman Це створило б нове Expressionпро ваші дії із обгорткою, але воно не матиме інформації про дерево вираження про внутрішні елементи dangerousCallделегата.
Ненад

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

1
Я хотів перейти синтаксичне дерево повернутого виразу. Чи дозволив би мені такий підхід зробити це?
Дейв Камерон

6
@DaveCameron - Ні. Див. Відповіді вище - вже складений Funcбуде приховано в новому виразі. Це просто додає один шар даних над кодом; ви можете пройти один шар просто для того, щоб знайти свій параметр fбез додаткових деталей, тож ви прямо там, де ви почали.
Jonno

21

Що ви, мабуть, повинні зробити, це обернути метод. Візьміть у вираз> і компілюйте та запустіть. Якщо це не вдається, у вас вже є вираз.

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

Якщо вам іноді потрібен вираз, а іноді і делегат, у вас є два варіанти:

  • мають різні методи (по 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();

Сервісно, ​​це як абсолютно законний спосіб отримати вираз. синтаксичний цукор, щоб побудувати його за допомогою express.lambda та express.call. Чому, на вашу думку, це має бути невдалим під час виконання?
Роман Покровський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.