konwertowanie .net Func <T> na .net Expression <Func <T>>


118

Przejście od lambdy do wyrażenia jest łatwe przy użyciu wywołania metody ...

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

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

Ale chciałbym zmienić Func w wyrażenie, tylko w rzadkich przypadkach ...

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

Linia, która nie działa, wyświetla błąd w czasie kompilacji Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Jawna obsada nie rozwiązuje sytuacji. Czy istnieje możliwość zrobienia tego, czego nie zauważam?


Naprawdę nie widzę pożytku z przykładu „rzadkiego przypadku”. Wzywający przekazuje w Func <T>. Nie ma potrzeby powtarzania wywołującemu, czym była ta Func <T> (poprzez wyjątek).
— Adam Ralph,

2
W programie wywołującym nie jest obsługiwany wyjątek. A ponieważ istnieje wiele witryn wywołujących przekazujących różne funkcje Func <T>, przechwycenie wyjątku w obiekcie wywołującym tworzy duplikację.
— Dave Cameron

1
Ślad stosu wyjątków służy do pokazywania tych informacji. Jeśli wyjątek zostanie zgłoszony w ramach wywołania Func <T>, zostanie to pokazane w śladzie stosu. Nawiasem mówiąc, gdybyś zdecydował się pójść w drugą stronę, tj. Zaakceptować wyrażenie i skompilować je do wywołania, straciłbyś to, ponieważ ślad stosu pokazywałby coś podobnego at lambda_method(Closure )do wywołania skompilowanego delegata.
— Adam Ralph

Myślę, że powinieneś spojrzeć na odpowiedź w tym [link] [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/…
— Ibrahim Kais Ibrahim

Odpowiedzi:


104

Ooh, to wcale nie jest łatwe. Func<T>reprezentuje rodzaj, delegatea nie wyrażenie. Jeśli można to zrobić w jakikolwiek sposób (z powodu optymalizacji i innych rzeczy wykonanych przez kompilator, niektóre dane mogą zostać wyrzucone, więc odzyskanie oryginalnego wyrażenia może być niemożliwe), byłby to demontaż IL w locie i wywnioskować wyrażenie (co wcale nie jest łatwe). Traktowanie wyrażeń lambda jako danych ( Expression<Func<T>>) jest magią wykonywaną przez kompilator (w zasadzie kompilator buduje drzewo wyrażeń w kodzie zamiast kompilować je do IL).

Powiązany fakt

Dlatego języki, które wypychają lambdy do skrajności (takie jak Lisp), są często łatwiejsze do zaimplementowania jako interpretery . W tych językach kod i dane są zasadniczo tym samym (nawet w czasie wykonywania ), ale nasz chip nie może zrozumieć tej formy kodu, więc musimy emulować taką maszynę, budując na niej interpreter, który ją rozumie ( wybór dokonany przez Lisp podobnie jak języki) lub poświęcenie mocy (kod nie będzie już dokładnie równy danym) do pewnego stopnia (wybór dokonany przez C #). W języku C # kompilator daje złudzenie traktowania kodu jako danych, umożliwiając interpretowanie wyrażeń lambda jako code ( Func<T>) i data ( Expression<Func<T>>) w czasie kompilacji .


3
Lispa nie trzeba interpretować, można go łatwo skompilować. Makra musiałyby zostać rozszerzone w czasie kompilacji, a jeśli chcesz je obsługiwać eval, musisz uruchomić kompilator, ale poza tym nie ma z tym żadnego problemu.
— konfigurator

2
"Expression <Func <T>> DangerousExpression = () => dangerousCall ();" nie jest łatwe?
— mheyman

10
@mheyman Stworzyłoby to nowe Expressioninformacje o akcji opakowania, ale nie zawierałoby informacji o drzewie wyrażeń o elementach wewnętrznych dangerousCalldelegata.
— Nenad

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

1
Chciałem przejść przez drzewo składni zwróconego wyrażenia. Czy takie podejście pozwoliłoby mi to zrobić?
— Dave Cameron

6
@DaveCameron - Nie. Patrz powyższe odpowiedzi - już skompilowane Funczostaną ukryte w nowym wyrażeniu. To po prostu dodaje jedną warstwę danych do kodu; możesz przejść przez jedną warstwę tylko po to, aby znaleźć swój parametr fbez dalszych szczegółów, więc jesteś od punktu wyjścia.
— Jonno

21

To, co prawdopodobnie powinieneś zrobić, to odwrócić tę metodę. Weź Expression>, skompiluj i uruchom. Jeśli to się nie powiedzie, masz już wyrażenie do sprawdzenia.

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

Oczywiście musisz wziąć pod uwagę wpływ tego na wydajność i określić, czy jest to coś, co naprawdę musisz zrobić.


7

Możesz jednak przejść w drugą stronę za pomocą metody .Compile () - nie jestem pewien, czy jest to przydatne:

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

Jeśli czasami potrzebujesz wyrażenia, a czasami potrzebujesz pełnomocnika, masz dwie opcje:

  • mają różne metody (po 1 dla każdego)
  • zawsze akceptuj Expression<...>wersję i po prostu .Compile().Invoke(...)ją, jeśli chcesz delegata. Oczywiście to kosztuje.

6

NJection.LambdaConverter to biblioteka konwertująca delegatów na wyrażenie

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

Czy możesz rozwinąć część „to nie zadziała”? Czy faktycznie próbowałeś go skompilować i wykonać? A może nie działa szczególnie w Twojej aplikacji?
— Dmitry Dzygin

1
FWIW, może nie o to chodziło w głównym bilecie, ale tego potrzebowałem. To była ta call.Targetczęść, która mnie zabijała. Działał przez lata, a potem nagle przestał działać i zaczął narzekać na statyczne / niestatyczne bla bla. W każdym razie dzięki!
— Eli Gassert


-1

Zmiana

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

Do

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

Servy, jest to całkowicie legalny sposób na uzyskanie ekspresji. cukier składniowy, aby go zbudować za pomocą expression.lambda i expression.call. Jak myślisz, dlaczego powinno to zawieść w czasie wykonywania?
— Roman Pokrovskij
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.