convertir una .net Func <T> en una .net Expression <Func <T>>

118

Pasar de una lambda a una expresión es fácil usando una llamada a un método ...

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

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

Pero me gustaría convertir el Func en una expresión, solo en casos raros ...

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 línea que no funciona me da el error en tiempo de compilación Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Un elenco explícito no resuelve la situación. ¿Hay alguna facilidad para hacer esto que estoy pasando por alto?

Dave Cameron
fuente
Realmente no veo mucha utilidad para el ejemplo de 'casos raros'. La persona que llama está pasando en el Func <T>. No hay necesidad de repetirle a la persona que llama lo que fue Func <T> (a través de la excepción).
Adam Ralph
2
La excepción no se maneja en la persona que llama. Y, debido a que hay varios sitios de llamadas que pasan en diferentes Func <T> s, detectar la excepción en el llamador crea una duplicación.
Dave Cameron
1
El seguimiento de la pila de excepciones está diseñado para mostrar esta información. Si la excepción se lanza dentro de la invocación de Func <T>, esto se mostrará en el seguimiento de la pila. Por cierto, si optara por ir al revés, es decir, aceptar una expresión y compilarla para invocarla, la perdería ya que el seguimiento de la pila mostraría algo parecido at lambda_method(Closure )a la invocación del delegado compilado.
Adam Ralph
Supongo que debería mirar la respuesta en este [enlace] [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/…
Ibrahim Kais Ibrahim

Respuestas:

104

Oh, no es nada fácil. Func<T>representa un genérico delegatey no una expresión. Si hay alguna forma de hacerlo (debido a las optimizaciones y otras cosas realizadas por el compilador, es posible que se descarten algunos datos, por lo que puede ser imposible recuperar la expresión original), sería desensamblar el IL sobre la marcha. e inferir la expresión (que de ninguna manera es fácil). Tratar las expresiones lambda como datos ( Expression<Func<T>>) es una magia realizada por el compilador (básicamente, el compilador crea un árbol de expresiones en el código en lugar de compilarlo en IL).

Hecho relacionado

Esta es la razón por la que los lenguajes que llevan las lambdas al extremo (como Lisp) suelen ser más fáciles de implementar como intérpretes . En esos lenguajes, el código y los datos son esencialmente lo mismo (incluso en tiempo de ejecución ), pero nuestro chip no puede entender esa forma de código, por lo que tenemos que emular una máquina así construyendo un intérprete sobre ella que la entienda (la elección hecha por Lisp como lenguajes) o sacrificando el poder (el código ya no será exactamente igual a los datos) hasta cierto punto (la elección hecha por C #). En C #, el compilador da la ilusión de tratar el código como datos al permitir que las lambdas se interpreten como código ( Func<T>) y datos ( Expression<Func<T>>) en tiempo de compilación .

Mehrdad Afshari
fuente
3
Lisp no tiene que ser interpretado, se puede compilar fácilmente. Las macros tendrían que expandirse en el momento de la compilación, y si desea admitirlo eval, deberá iniciar el compilador, pero aparte de eso, no hay ningún problema en hacerlo.
configurador
2
"Expresión <Func <T>> DangerousExpression = () => dangerousCall ();" ¿no es fácil?
mheyman
10
@mheyman Eso crearía algo nuevo Expressionsobre su acción de envoltura, pero no tendría información de árbol de expresión sobre los aspectos internos del dangerousCalldelegado.
Nenad
34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 
Anular
fuente
1
Quería recorrer el árbol de sintaxis de la expresión devuelta. ¿Este enfoque me permitiría hacer eso?
Dave Cameron
6
@DaveCameron - No. Consulte las respuestas anteriores: lo ya compilado Funcse ocultará en una nueva expresión. Esto simplemente agrega una capa de datos sobre el código; podría atravesar una capa solo para encontrar su parámetro fsin más detalles, por lo que está justo donde comenzó.
Jonno
21

Lo que probablemente debería hacer es cambiar el método. Tome una Expresión>, compile y ejecute. Si falla, ya tiene la expresión para analizar.

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

Obviamente, debe considerar las implicaciones de rendimiento de esto y determinar si es algo que realmente necesita hacer.

David Wengier
fuente
7

Sin embargo, puede ir al revés a través del método .Compile (); no estoy seguro de si esto es útil para usted:

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

Si a veces necesita una expresión y otras veces necesita un delegado, tiene 2 opciones:

  • tienen diferentes métodos (1 para cada uno)
  • Acepta siempre la Expression<...>versión, y solo .Compile().Invoke(...)si quieres un delegado. Evidentemente esto ha costado.
Marc Gravell
fuente
6

NJection.LambdaConverter es una biblioteca que convierte delegados en expresión

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)
    } 
}
Sagi
fuente
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);
        }
Dmitry Dzygin
fuente
¿Puede elaborar la parte de "esto no funcionará"? ¿De verdad ha intentado compilarlo y ejecutarlo? ¿O no funciona particularmente en su aplicación?
Dmitry Dzygin
1
FWIW, esto podría no ser de lo que se trataba el boleto principal, pero era lo que necesitaba. Era la call.Targetparte que me estaba matando. Funcionó durante años, y luego de repente dejó de funcionar y comenzó a quejarse de un bla, bla, bla estático / no estático. ¡Gracias de todos modos!
Eli Gassert
-1

Cambio

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

A

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();
mheyman
fuente
Servy, es una forma absolutamente legal de expresarse. azúcar de sintaxis para construirlo a través de expression.lambda y expression.call. ¿Por qué crees que debería fallar en tiempo de ejecución?
Roman Pokrovskij