web-dev-qa-db-fra.com

conversion d'un .net Func <T> en une expression .net <Func <T>>

Passer d'un lambda à une expression est facile en utilisant un appel de méthode ...

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

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

Mais je voudrais transformer le Func en une expression, seulement dans de rares cas ...

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

La ligne qui ne fonctionne pas me donne l'erreur de compilation Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Une distribution explicite ne résout pas la situation. Y a-t-il une installation pour ce faire que je néglige?

111
Dave Cameron

Ooh, ce n'est pas facile du tout. Func<T> représente un delegate générique et non une expression. S'il y a un moyen de le faire (en raison des optimisations et d'autres choses effectuées par le compilateur, certaines données peuvent être supprimées, il peut donc être impossible de récupérer l'expression d'origine), ce serait le démontage de l'IL à la volée et inférer l'expression (ce qui n'est pas du tout facile). Traitement des expressions lambda en tant que données (Expression<Func<T>>) est une magie effectuée par le compilateur (fondamentalement, le compilateur construit un arbre d'expression en code au lieu de le compiler en IL).

Fait connexe

C'est pourquoi les langages qui poussent les lambdas à l'extrême (comme LISP) sont souvent plus faciles à implémenter comme interprètes. Dans ces langues, le code et les données sont essentiellement la même chose (même à au moment de l'exécution ), mais notre puce ne peut pas comprendre cette forme de code, nous avons donc pour émuler une telle machine en construisant un interprète au-dessus d'elle qui la comprend (le choix fait par LISP comme les langages) ou en sacrifiant la puissance (le code ne sera plus exactement égal aux données) dans une certaine mesure (le choix fait par C #) . En C #, le compilateur donne l'illusion de traiter le code comme des données en permettant aux lambdas d'être interprétés comme code (Func<T>) et données (Expression<Func<T>>) à temps de compilation .

98
Mehrdad Afshari
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 
27
Override

Ce que vous devriez probablement faire, c'est inverser la méthode. Prenez une Expression>, puis compilez et exécutez. S'il échoue, vous avez déjà l'expression à examiner.

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

De toute évidence, vous devez prendre en compte les implications en termes de performances et déterminer si c'est quelque chose que vous devez vraiment faire.

21
David Wengier

Cependant, vous pouvez aller dans l'autre sens via la méthode .Compile () - vous ne savez pas si cela vous est utile:

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

INJection.Lambda Converter est une bibliothèque qui convertit les délégués en expression

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)
    } 
}
5
Sagi

Si vous avez parfois besoin d'une expression et parfois d'un délégué, vous avez 2 options:

  • avoir des méthodes différentes (1 pour chaque)
  • acceptez toujours la version Expression<...>, et juste .Compile().Invoke(...) si vous voulez un délégué. Évidemment, cela a coûté.
4
Marc Gravell
 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);
        }
4
Dmitry Dzygin

JB Evain de l'équipe Cecil Mono fait des progrès pour permettre cette

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

3
aaguiar