2010-03-10 11 views
6

Supongamos que tengo las siguientes expresiones:¿Cómo puedo combinar varias expresiones en un método rápido?

Expression<Action<T, StringBuilder>> expr1 = (t, sb) => sb.Append(t.Name); 
Expression<Action<T, StringBuilder>> expr2 = (t, sb) => sb.Append(", "); 
Expression<Action<T, StringBuilder>> expr3 = (t, sb) => sb.Append(t.Description); 

me gustaría ser capaz de compilar estos en un método/delegar equivalente a lo siguiente:

void Method(T t, StringBuilder sb) 
{ 
    sb.Append(t.Name); 
    sb.Append(", "); 
    sb.Append(t.Description); 
} 

¿Cuál es la mejor manera de acercarse ¿esta? Me gustaría que funcione bien, idealmente con un rendimiento equivalente al método anterior.

ACTUALIZACIÓN Así, mientras que parece que no hay manera de hacerlo directamente en C# 3 ¿Hay una manera de convertir una expresión de IL para que pueda usarlo con System.Reflection.Emit?

+0

Usted quiere ser capaz de escribir algo como: 'Expresión > combinedExperssion = expr1 + + expr2 expr3;'? –

Respuesta

4

Desafortunadamente en .NET 3.5 no se puede construir una expresión que realiza una serie de operaciones arbitrarias. Aquí está la lista de expresiones compatibles:

  • Aritmética: añadir, AddChecked, Dividir, Modulo, Multiplicar, MultiplyChecked, niega, NegateChecked, potencia, Restar, SubtractChecked, UnaryPlus
  • Creación: Bind, ElementInit, ListBind, ListInit , MemberBind, MemberInit, Nuevo, NewArrayBounds, NewArrayInit
  • bit a bit: Y, ExclusiveOr, IzquierdaSHIFT (< <), no, o, SHIFTDERECHA (>>)
  • lógico:? AndAlso (& &), Condición (:) , Equal, GreaterThan, GreaterThanOrEqual, LessThan, * LessThanOrEqual, No igual, OrElse (||), TypeIs
  • miembro de acceso: ArrayIndex, ArrayLength, Llamada, campo, propiedad PropertyOrField
  • Otros: Convertir, ConvertChecked, Coalesce (??), Constant, invocación, Lambda, Parámetro, Tipo Como, Cita

.NET 4 se extiende esta API mediante la adición de las siguientes expresiones:

  • Mutación: addAssign, AddAssignChecked, AndAssign, asignar, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign , RightShiftAssign, SubtractAssign, SubtractAssignChecked
  • aritmética: Menos, por defecto, Incremento, OnesComplement
  • acceso de usuario: ArrayAccess, dinámico
  • lógico: ReferenceEqual, ReferenceNotEqual, TypeEqual
  • flujo: Bloque, break, continue, Vacío, Goto, IfThen, IfThenElse, ifFalse, ifTrue, Etiqueta, Loop, Volver, Switch, SwitchCase, Unbox, variables
  • Excepciones: Catch, volver a lanzar, banda
  • de depuración: ClearDebugInfo, DebugInfo

La expresión Block es particularmente interesante.

+0

Mitsu Furuta de MS 'demostró' una forma de combinar expresiones usando C# 4 y algunos trucos durante los TechDays franceses en París. Consulte su blog, y particularmente esta publicación: http://blogs.msdn.com/mitsu/archive/2010/03/02/c-4-expressions-blocks-part-i.aspx –

+0

Aquí hay otra publicación de blog que puede encontrar interesante: http://community.bartdesmet.net/blogs/bart/archive/2009/08/10/expression-trees-take-two-introducing-system-linq-expressions-v4-0.aspx –

0

Solo puede hacer eso en .NET 4. Lamento no conocer los detalles.

Editar:

Si se siente cómodo con Reflection.Emit, se puede emitir un método llamando a esas expresiones en secuencia.

Otra alternativa:

crear un método de 'hacer', es decir:

void Do(params Action[] actions) 
{ 
    foreach (var a in actions) a(); 
} 
1

Puede hacerlo, pero no es una tarea trivial.

Cuando tiene una variable de tipo Expresión, puede inspeccionar su propiedad de Cuerpo para encontrar la estructura de datos de la expresión.

No puede pedirle al compilador que lo compile porque no obtendrá el resultado que desea. Tendrás que analizar los cuerpos de todas tus expresiones y combinarlas de algún modo en un único método, todo al emitir IL al mismo tiempo (o, al producir C# y compilarlo si sientes que IL está un paso demasiado lejos).

Así como LINQ-to-SQL compila la expresión de una consulta SQL, también puede compilar sus expresiones en lo que necesite. Tendrá mucho trabajo por delante, pero solo necesita implementar eso para lo que quiere apoyar.

En este caso bastante trivial, no creo que sea necesario crear su propio proveedor de LINQ. Podrías simplemente trabajar con la expresión como aprobada e ir desde allí. Pero sospecho que tu aplicación es un poco más complicada que eso.

1

En 4.0 esto es mucho más fácil gracias al soporte para operaciones de bloques en el árbol (aunque no en el compilador de expresiones C#).

Sin embargo, puede hacer esto explotando el hecho de que StringBuilder expone una API "fluida"; así que en vez de Action<T,StringBuilder> tiene una Func<T,StringBuilder,StringBuilder> -, como a continuación (tenga en cuenta que la sintaxis real para expresar estas expresiones es idéntica en este caso):

class Program 
{ 
    static void Main() 
    { 
     Foo(new MyType { Name = "abc", Description = "def" }); 
    } 
    static void Foo<T>(T val) where T : IMyType 
    { 
     var expressions = new Expression<Func<T, StringBuilder, StringBuilder>>[] { 
       (t, sb) => sb.Append(t.Name), 
       (t, sb) => sb.Append(", "), 
       (t, sb) => sb.Append(t.Description) 
     }; 
     var tparam = Expression.Parameter(typeof(T), "t"); 
     var sbparam = Expression.Parameter(typeof(StringBuilder), "sb"); 

     Expression body = sbparam; 
     for (int i = 0; i < expressions.Length; i++) 
     { 
      body = Expression.Invoke(expressions[i], tparam, body); 
     } 
     var func = Expression.Lambda<Func<T, StringBuilder, StringBuilder>>(
      body, tparam, sbparam).Compile(); 

     // now test it 
     StringBuilder sbInst = new StringBuilder(); 
     func(val, sbInst); 
     Console.WriteLine(sbInst.ToString()); 
    } 
} 
public class MyType : IMyType 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 
interface IMyType 
{ 
    string Name { get; } 
    string Description { get; } 
} 

Sin duda, es posible para inspeccionar los árboles y emiten IL manualmente (DynamicMethod quizás), pero tendría que tomar algunas decisiones sobre la limitación de la complejidad. Para el código como se presentó podría hacerlo en razonable vez (aún no es trivial), pero si espera algo más complejo Expression es más su frito.

0

Otra forma de ver este problema es recordar que los delegados son de transmisión múltiple; puede combinar un Action muchas veces;

class Program 
{ 
    static void Main() 
    { 
     Foo(new MyType { Name = "abc", Description = "def" }); 
    } 

    static void Foo<T>(T val) where T : IMyType { 
     var expressions = new Expression<Action<T, StringBuilder>>[] { 
       (t, sb) => sb.Append(t.Name), 
       (t, sb) => sb.Append(", "), 
       (t, sb) => sb.Append(t.Description) 
     }; 
     Action<T, StringBuilder> result = null; 
     foreach (var expr in expressions) result += expr.Compile(); 
     if (result == null) result = delegate { }; 
     // now test it 
     StringBuilder sbInst = new StringBuilder(); 
     result(val, sbInst); 
     Console.WriteLine(sbInst.ToString()); 
    } 
} 
public class MyType : IMyType 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 
interface IMyType 
{ 
    string Name { get; } 
    string Description { get; } 

} 
Cuestiones relacionadas