2010-01-10 14 views
29

He estado trabajando con árboles de expresiones durante unos días y tengo curiosidad por saber qué hace Expression.Reduce(). El msdn documentation no es muy útil, ya que solo indica que "reduce" la expresión. Por si acaso, intenté con un ejemplo (ver a continuación) para comprobar si este método incluía la reducción matemática, pero este no parece ser el caso.¿Qué hace Expression.Reduce()?

¿Alguien sabe lo que hace este método y es posible proporcionar un ejemplo rápido que lo muestra en acción? ¿Algún buen recurso por ahí?

static void Main(string[] args) 
{ 
    Expression<Func<double, double>> func = x => (x + x + x) + Math.Exp(x + x + x); 
    Console.WriteLine(func); 
    Expression r_func = func.Reduce(); 
    Console.WriteLine(r_func); // This prints out the same as Console.WriteLine(func) 
} 
+0

Su ejemplo probablemente sea defectuoso. Marque 'CanReduce' para ver si la llamada de reducción realmente hará algo. –

+3

Claro, devuelve falso. Mi pregunta, en otras palabras, sería: ¿Cuándo regresa Expression.CanReduce verdadero? –

+0

Cuando la expresión se puede reducir a una "más simple". Mi suposición de "más simple" se basaría en la representación interna: una expresión "más simple" es aquella que tiene una representación interna más pequeña y/o más rápida. –

Respuesta

23

el documento que tenga que mirar es " expr-tree-spec.doc "aquí: http://dlr.codeplex.com/wikipage?title=Docs%20and%20specs&referringTitle=Documentation

Esta es la especificación para los árboles de expresiones. Lea las secciones "2.2 Nodos Reducibles" y "4.3.5 Reducir Método".

Básicamente, este método está destinado a las personas que implementan o trasladan sus lenguajes dinámicos a .NET. Para que puedan crear sus propios nodos que pueden "reducir" a nodos de árbol de expresión estándar y se pueden compilar. Hay algunos nodos "reducibles" en la API de árboles de expresión, pero no sé si se pueden obtener ejemplos prácticos (ya que todos los nodos de expresión estándar compilan de todos modos, ya que al usuario final probablemente no le importa si son "reducidos"). "detrás de la escena o no").

Sí, documentación de MSDN es muy básico en esta área, ya que la principal fuente de información y documentación para los ejecutores del lenguaje es http://dlr.codeplex.com/

+0

Esto es genial. Muchas gracias por los recursos también (BTW gran blog). –

+0

De nada. –

+0

La falta de documentación estaría bien si Microsoft se molestara en mencionar dónde encontrar la documentación "real" ... – Qwertie

0

Supongo que es más para que diferentes proveedores de linq los usen para transformar ciertos tipos de nodos en una representación más simple.

ya que los documentos son escasos, podrían usarse para common subexpression elimination para eliminar expresiones redundantes. si su función calcula x + x más de una vez sin cambiar la x local, puede simplificarla guardando el resultado de la primera expresión en un temporal. tal vez correspondería al proveedor de linq implementar estas transformaciones de forma opcional.

o si se había anidado BlockExpressions que contenía ningún código (una expresión como {{{}}}), los que se podrían eliminar, o un ConditionalExpression vacío ...

+0

Acabo de reducir x => (x + x + x) + Math.Exp (x + x + x) pero tampoco pareció hacer nada ... –

20

Con un poco de desmontaje, me encontré con que Expression.CanReduce siempre vuelve a nutrir false y Expression.Reduce() siempre devuelve this. Sin embargo, hay algunos tipos que anulan ambos. LambdaExpression hereda las implementaciones predeterminadas, lo que explica por qué las expresiones que se han intentado hasta ahora no funcionan.

Uno de los tipos que anula Reducir() es MemberInitExpression, lo que me llevó a la siguiente experimento exitoso:

class ReduceFinder : ExpressionVisitor { 
    public override Expression Visit(Expression node) { 
     if (node != null && node.CanReduce) { 
      var reduced = node.Reduce(); 
      Console.WriteLine("Found expression to reduce!"); 
      Console.WriteLine("Before: {0}: {1}", node.GetType().Name, node); 
      Console.WriteLine("After: {0}: {1}", reduced.GetType().Name, reduced); 
     } 
     return base.Visit(node); 
    } 
} 

class Foo { 
    public int x; 
    public int y; 
} 

static class Program { 
    static void Main() { 
     Expression<Func<int, Foo>> expr = z => new Foo { x = (z + 1), y = (z + 1) }; 
     new ReduceFinder().Visit(expr); 
    } 
} 

Salida:

Found expression to reduce! 
Before: MemberInitExpression: new Foo() {x = (z + 1), y = (z + 1)} 
After: ScopeN: { ... } 
+0

¡Muchas gracias por su ejemplo! –

+2

¿No puedo aceptar dos respuestas diferentes? –

+0

Lamentablemente, no, pero habría aceptado la de Alexandra también, así que no te preocupes. :) Sin embargo, puedes votar las dos respuestas si aún no lo has hecho ... –

4

Esta es una pregunta bastante antiguo, pero parece tener un Un poco de interés, así que estoy agregando esta respuesta extra con información sobre lo que hace el producto .NET listo para usar en este momento.

Por lo que puedo decir, Reduce() solo se anula en operaciones complejas que implementan una tarea como parte de su trabajo. Parece que hay tres escenarios clave.

  1. Las asignaciones compuestas se expanden a operaciones de aritmética y asignación binarias discretas; en otras palabras,

    x += y

    convierte

    x = x + y.

  2. Los operadores de incremento previo y posterior se amplían a sus operaciones discretas. Para pre-incremento/decrementos,

    ++x

    se convierte en aproximadamente:

    x = x + 1

    y

    x++

    se convierte en aproximadamente:

    temp = x; 
    x = x + 1; 
    temp; 
    

    digo aproximadamente porque la operación no se lleva a cabo como una operación binaria x + 1 con ser el operando de la izquierda x y ser el operando de la derecha la constante 1 sino como una operación de incremento/decremento unario. El efecto neto es el mismo.

  3. Los inicializadores de miembros y listas se han ampliado desde su forma abreviada a su forma larga. Por lo tanto:

    new Thing() { Param1 = 4, Param2 = 5 }

    se convierte en:

    temp = new Thing(); 
    temp.Param1 = 4; 
    temp.Param2 = 5; 
    temp; 
    

    y:

    new List<int>() { 4, 5 }

    se convierte en:

    temp = new List<int>(); 
    temp.Add(4); 
    temp.Add(5); 
    temp; 
    

Si estos cambios facilitan o dificultan que una persona implemente algo que analiza un árbol de expresiones es una cuestión de opinión, pero la conclusión es que ese es el nivel de reducción que parece salir de la caja en .NET marco de referencia.