2010-10-21 12 views
18

me gustaría decir¿Puedo capturar una variable local en una expresión LINQ como una constante en lugar de una referencia de cierre?

int x = magic(), y = moremagic(); 
return i => i + (x/y); 

y tienen el x ser capturado como una constante en lugar de una referencia variable. La idea es que x nunca cambiará y entonces cuando la expresión se compila más adelante, el compilador puede hacer un plegado constante y producir un código más eficiente, es decir, calculando x/y una vez en vez de cada llamada, mediante desreferencias de puntero en un registro de cierre.

No hay manera de marcar x como readonly dentro de un método, y el compilador no es lo suficientemente inteligente como para detectar que no cambia después de la creación de la expresión.

No me gustaría tener que construir la expresión a mano. ¿Alguna idea brillante?

ACTUALIZACIÓN: Terminé usando la maravillosa LinqKit para construir un evaluador parcial que va a hacer las sustituciones que quiero. La transformación solo es segura si sabes que las referencias relevantes no cambiarán, pero funcionó para mis propósitos. Es posible restringir la evaluación parcial solo a los miembros directos de su cierre, que usted controla, agregando un cheque adicional o dos, lo que es bastante obvio al inspeccionar el código de muestra proporcionado en el LinqKit.

/// <summary>Walks your expression and eagerly evaluates property/field members and substitutes them with constants. 
/// You must be sure this is semantically correct, by ensuring those fields (e.g. references to captured variables in your closure) 
/// will never change, but it allows the expression to be compiled more efficiently by turning constant numbers into true constants, 
/// which the compiler can fold.</summary> 
public class PartiallyEvaluateMemberExpressionsVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitMemberAccess(MemberExpression m) 
    { 
     Expression exp = this.Visit(m.Expression); 

     if (exp == null || exp is ConstantExpression) // null=static member 
     { 
      object @object = exp == null ? null : ((ConstantExpression)exp).Value; 
      object value = null; Type type = null; 
      if (m.Member is FieldInfo) 
      { 
       FieldInfo fi = (FieldInfo)m.Member; 
       value = fi.GetValue(@object); 
       type = fi.FieldType; 
      } 
      else if (m.Member is PropertyInfo) 
      { 
       PropertyInfo pi = (PropertyInfo)m.Member; 
       if (pi.GetIndexParameters().Length != 0) 
        throw new ArgumentException("cannot eliminate closure references to indexed properties"); 
       value = pi.GetValue(@object, null); 
       type = pi.PropertyType; 
      } 
      return Expression.Constant(value, type); 
     } 
     else // otherwise just pass it through 
     { 
      return Expression.MakeMemberAccess(exp, m.Member); 
     } 
    } 
} 
+0

justo lo que estaba buscando! – jeroenh

+1

Sé que ha pasado un tiempo, pero esto me ha ahorrado mucho tiempo. Gracias. – Stargazer

Respuesta

4

No, no hay forma de hacerlo en C#. El compilador no admite la captura de variables por valor/const. Tampoco puede convertir un valor no const en un valor constante en el tiempo de ejecución de esta manera.

Además, el compilador C# solo dobla constantemente durante la compilación inicial para conocer valores constantes. Si fuera posible congelar un valor en el tiempo de ejecución en una constante, no participaría en el plegado de constantes del compilador porque ocurre en el tiempo de ejecución.

2

El compilador no hace este tipo de "almacenamiento en caché de valores". El plegado constante se realiza en tiempo de compilación para constantes solamente, no para campos de solo lectura y ciertamente no para variables locales que no tienen un valor conocido en tiempo de compilación.

Tienes que hacer esto tú mismo, pero tiene que permanecer como referencia de cierre (ya que el valor no es determinable en tiempo de compilación, por lo que es probable que se ponga en el cierre cuando se construye la expresión) :

int x = magic(), y = moremagic(); 
int xy = x/y; 
return i => i + xy; 
+0

de hecho, aunque esto todavía implica que se busque xy en cada uso. Y mi ejemplo es arbitrariamente complejo: sería bueno que el compilador de C# simplificara la ecuación grande resultante, en lugar de hacerlo yo mismo. –

0

x no puede ser una constante, porque estás haciendo magia en tiempo de ejecución para determinar de qué se trata. Sin embargo, si se sabe que x y y no cambian, intente:

int x = magic(), y = moremagic(); 
int xOverY = x/y; 
return i => i + xOverY; 

También debería mencionar que a pesar de que el código compilado para IL i => i + (x/y) mostrará la división, el compilador JIT es casi seguro que va a optimizar Por aqui.

+0

No creo que pueda optimizar la división, ya que tiene que buscar la x y la y desde el cierre cada vez –

+0

Quizás tenga razón. No sé mucho sobre el compilador JIT de .NET. Me parece que sería muy fácil para el compilador reconocer que los valores de xey nunca cambiarán para una instancia particular del cierre (básicamente se guardan como campos de solo lectura en una nueva clase, basándose en el IL), y modificar la clase en tiempo de ejecución para eliminar la necesidad de ellos. – StriplingWarrior

0

Una técnica que utilicé en vb2005 fue utilizar una fábrica de delegados genéricos para efectuar cierres con valores porosos. Solo lo implementé para subs en lugar de funciones, pero también podría hacerlo para funciones. Si se extiende de esa manera:

 
FunctionOf.NewInv() 

habría una función estática que aceptar como parámetros una función (descrito más adelante), un T3, y T4. La función transferida debe aceptar parámetros de los tipos T2, T3 y T4, y devolver un T1.La función devuelta por NewInv aceptará un parámetro de tipo T2 y llamará a la función transferida con ese parámetro y los dados a NewInv.

La invocación sería algo como:

 
return FunctionOf.NewInv((i,x,y) => i+x/y, x, y) 
-1

Si usted (como yo) está creando alguna generador de expresiones para consultas SQL puede consirer el siguiente: en primer lugar crear una variable de clase, lo convierten en una constante y luego acceda de esta manera:

var constant= Expression.Constant(values); 
var start = Expression.MakeMemberAccess(constant, values.GetMemberInfo(f => f.Start)); 
var end = Expression.MakeMemberAccess(constant, values.GetMemberInfo(f => f.End)); 

var more = Expression.GreaterThanOrEqual(memberBody, start); 
var less = Expression.LessThanOrEqual(memberBody, end); 
+0

No hay ninguna ventaja al hacer esto solo por usar un cierre. – Servy

+0

@Servy ¿cómo se crea un cierre con expresiones? Hubo valores en mis consultas con Expression.Constant now there are> @parameters – xumix

+0

Utiliza una lambda y cierra la variable, como se muestra en like all of the other answers, y la pregunta. – Servy

Cuestiones relacionadas