2011-12-14 13 views
7

Si quisiera crear un árbol de expresiones que llamara a un método con un parámetro out y luego devolviera el valor out como resultado ... ¿cómo lo haría?Parámetros de ByRef con árboles de expresiones en C#

El siguiente no funciona (una excepción de tiempo de ejecución), pero quizás mejor demuestra lo que estoy tratando de hacer:

private delegate void MyDelegate(out int value); 
private static Func<int> Wrap(MyDelegate dele) 
{ 
    MethodInfo fn = dele.Method; 
    ParameterExpression result = ParameterExpression.Variable(typeof(int)); 
    BlockExpression block = BlockExpression.Block(
     typeof(int), // block result 
     Expression.Call(fn, result), // hopefully result is coerced to a reference 
     result); // return the variable 
    return Expression.Lambda<Func<int>>(block).Compile(); 
} 

private static void TestFunction(out int value) 
{ 
    value = 1; 
} 

private static void Test() 
{ 
    Debug.Assert(Wrap(TestFunction)() == 1); 
} 

Sé que esto puede resolverse con bastante facilidad en crudo IL (o, de hecho, sin compilación en tiempo de ejecución), pero desafortunadamente esto es parte de un proceso de construcción de expresiones mucho más grande ... así que realmente espero que esto no sea una limitación, ya que una reescritura completa sería más que un dolor.

+1

funciones Lambda sin duda puede llamar a métodos que tienen 'ref' /' parámetros out' (como en cuestión), lo que no pueden hacer es referirse a 'ref' del método de encerramiento /' parámetros out'. – Mania

Respuesta

7

Esto funciona para mí:

private static Func<int> Wrap(MyDelegate dele) 
    { 
     var fn = dele.Method; 
     var result = ParameterExpression.Variable(typeof(int)); 
     var block = BlockExpression.Block(
      typeof(int), 
      new[] { result }, 
      new Expression[] 
      { 
       Expression.Call(fn, result), 
       result, 
      }); 
     return Expression.Lambda<Func<int>>(block).Compile(); 
    } 
-4

Tal vez sea sólo yo, pero yo no veo el punto de todo el asunto. Para lograr lo que estás tratando de hacer, realmente no necesitas escribir todo eso.

código de ejemplo en una aplicación de consola:

class Program 
    { 
     static void Main(string[] args) 
     { 
      var temp = Execute(DoSomething); 
      Console.Write(temp); 
      Console.Read(); 
     } 

     static int Execute(Func<int> methodToRun) 
     { 
      return methodToRun.Invoke(); 
     } 

     static int DoSomething() 
     { 
      return 1; 
     } 
    } 

Como se puede ver que le consigue los mismos resultados de una manera más concisa y limpia. Lo que creo que estábamos perdiendo es que Action, Action<> y Func<> son todo el azúcar sintáctica para delegate así que no hay necesidad de mezclar las 2 sintaxis y no hay necesidad de reconstruir toda la expresión como la que está haciendo.

+0

El objetivo del ejercicio es la generación de código de tiempo de ejecución. es decir, ¿qué pasaría si se le pasara un 'MethodInfo' completamente arbitrario y deseara poder mezclar un poco los resultados/parámetros con la mínima penalización de rendimiento posible? No se pudo llamar al método directamente, ya que no se conoce el detalle en tiempo de compilación, se podría usar el reflejo para invocar el método en tiempo de ejecución, pero eso sería muy lento. Expression.Compile te permite hacer un Poco más trabajo la primera vez, pero crea un método que te permita hacerlo repetidamente a un costo mínimo para futuras invocaciones. Este es el punto;) – Mania

+0

Bueno, ahora que lo explicas, pero desde el código que escribiste no apareció así. Aún así, delegar MyDelegate (out int value) es equivalente a Func MyDelegate. Func es también el tipo de devolución de Wrap. Eso, para mí, parece inútil: el método consigue un Func como parámetro y da salida a la misma Func como resultado ... no, todavía no es convincente :) –

+0

Leer el bit inmediatamente después del código de ejemplo;), reconocí que el tiempo de ejecución No se requiere generación de código aquí, pero explicó que es parte de un problema mayor. El código de muestra solo sirvió para demostrar una versión mínima y simple del problema en cuestión: para incluir el código completo se necesitarían varios archivos .cs grandes. Consideré hacer el caso de prueba 'int.TryParse' (que devuelve' bool' y 'out int'), y requiriendo que Expression los combinara en un' Tuple' .. pero eso parecía innecesariamente complicado. (Y también podría ser resuelto sin generación de código de tiempo de ejecución) – Mania