2008-09-21 8 views
7

tengo el siguiente código:Prevenir .NET de "lifting" variables locales

string prefix = "OLD:"; 
Func<string, string> prependAction = (x => prefix + x); 
prefix = "NEW:"; 
Console.WriteLine(prependAction("brownie")); 

Debido a que el compilador reemplaza la variable prefijo con un cierre "NUEVO: brownie" se imprime en la consola.

¿Hay alguna manera fácil de evitar que el compilador eleve la variable de prefijo mientras sigue usando una expresión lambda? Me gustaría una manera de hacer mi trabajo Func idéntica a:

Func<string, string> prependAction = (x => "OLD:" + x); 

La razón que necesito esto es me gustaría serializar el delegado resultante. Si la variable de prefijo está en una clase no serializable, la función anterior no se serializará.

La única forma de evitar esto puedo ver en este momento es la creación de una nueva clase serializable que almacena la cadena como una variable miembro y tiene el método de cadena Prefijo:

string prefix = "NEW:"; 
var prepender = new Prepender {Prefix = prefix}; 
Func<string, string> prependAction = prepender.Prepend; 
prefix = "OLD:"; 
Console.WriteLine(prependAction("brownie")); 

Con clase de ayuda:

[Serializable] 
public class Prepender 
{ 
    public string Prefix { get; set; } 
    public string Prepend(string str) 
    { 
     return Prefix + str; 
    } 
} 

Esto parece mucho trabajo adicional para hacer que el compilador sea "tonto".

+0

¿Entonces su pregunta no es sobre la evaluación perezosa, sino sobre la serialización? – Sam

Respuesta

8

Veo el problema subyacente ahora. Es más profundo de lo que pensé primero. Básicamente, la solución es modificar el árbol de expresiones antes de serializarlo, reemplazando todos los subárboles que no dependen de los parámetros con nodos constantes. Esto aparentemente se llama "funcletización". Hay una explicación de ello here.

+0

El enlace msdn estaba roto; Creo que el enlace correcto ahora es http://social.msdn.microsoft.com/Forums/en-US/67f63b9a-ea44-4428-aea0-5dcdb61e918b/binding-lambdas-when-they-are-closures Lamentablemente el código existe usa FuncletExpression, una clase que creo que ha sido eliminada del framework neto. – HugoRune

+0

@HugoRune: Gracias por el enlace actualizado. Me imagino que el código funciona, si solo quita el caso de FuncletExpression (y agrega casos para cualquier ExpressionTypes que se haya agregado desde entonces). –

+0

Alternativamente, busque esto: http://dotnetinside.com/framework/v4.0.30319/System.Data.Linq/Funcletizer –

-1

¿Qué pasa con este

string prefix = "OLD:"; 
string _prefix=prefix; 
Func<string, string> prependAction = (x => _prefix + x); 
prefix = "NEW:"; 
Console.WriteLine(prependAction("brownie")); 
+0

Esto no resuelve el problema de serialización ya que la variable _prefix aún está unida a la clase que contiene el código. – Brownie

-1

¿Qué tal:

string prefix = "OLD:"; 
string prefixCopy = prefix; 
Func<string, string> prependAction = (x => prefixCopy + x); 
prefix = "NEW:"; 
Console.WriteLine(prependAction("brownie")); 

?

+0

Esto no resuelve el problema de serialización ya que la variable prefixCopy aún está asociada a la clase que contiene el código. – Brownie

1

Lambdas 'chupan' automáticamente en las variables locales, me temo que es así como funcionan por definición.

0

Este es un problema bastante común es decir, variables que se modifican mediante un cierre sin querer - una solución mucho más simple es sólo para ir:

string prefix = "OLD:"; 
var actionPrefix = prefix; 
Func<string, string> prependAction = (x => actionPrefix + x); 
prefix = "NEW:"; 
Console.WriteLine(prependAction("brownie")); 

Si está utilizando ReSharper lo que realmente va a identificar los lugares en su código donde corres el riesgo de causar efectos secundarios inesperados como este, por lo tanto, si el archivo es "todo verde", tu código debería estar bien.

creo que de alguna manera habría sido bueno si tuviéramos un poco de azúcar sintáctica para manejar esta situación así que podría haber escrito como una sola línea, es decir

Func<string, string> prependAction = (x => ~prefix + x); 

Donde algunos operador de prefijo haría que el el valor de la variable a evaluar antes de construir el delegado/función anónimo.

+0

Creo que te perdiste el sentido de la pregunta. Su delegado no podrá serializar porque aún se ha formado un cierre, solo en una variable diferente. –

0

Ya hay varias respuestas aquí que explican cómo evitar que la lambda "levante" su variable. Desafortunadamente eso no resuelve tu problema subyacente. Ser incapaz de serializar el lambda no tiene nada que ver con que la lambda haya "levantado" tu variable. Si la expresión lambda necesita una instancia de una clase que no sea de serialización para calcular, tiene perfecto sentido que no se pueda serializar.

Dependiendo de lo que realmente está tratando de hacer (no puedo decidir por su publicación), una solución sería mover la parte no serializable de la lambda al exterior.

Por ejemplo, en lugar de:

NonSerializable nonSerializable = new NonSerializable(); 
Func<string, string> prependAction = (x => nonSerializable.ToString() + x); 

uso:

NonSerializable nonSerializable = new NonSerializable(); 
string prefix = nonSerializable.ToString(); 
Func<string, string> prependAction = (x => prefix + x); 
+0

Si ese código está contenido dentro de un método no serializable (en mi caso, un código ASP.NET detrás), prependAction aún se referirá a esa clase y no se serializará. – Brownie

-1

Bueno, si vamos a hablar de "problemas" aquí, lambdas provienen del mundo de la programación funcional, y en un lenguaje de programación puramente funcional, no hay asignaciones y entonces su problema nunca surgirá porque el valor del prefijo nunca podría cambiar. Entiendo que C# piensa que es genial importar ideas de programas funcionales (porque FP es genial) pero es muy difícil hacerlo bonito, porque C# es y siempre será un lenguaje de programación imperativo.

0

Me sale el problema ahora: el lambda se refiere a la clase contenedora que podría no ser serializable. A continuación, hacer algo como esto:

public void static Func<string, string> MakePrependAction(String prefix){ 
    return (x => prefix + x); 
} 

(. Tenga en cuenta la palabra clave static) A continuación, el lambda necesita no hacen referencia a la clase que contiene.

+0

¡Casi funcionó! Desafortunadamente, el compilador generó una clase sellada privada para mantener la variable de prefijo. La clase generada por el compilador no es serializable. – Brownie

2

Simplemente haga otro cierre ...

decir, algo así como:

var prepend = "OLD:"; 

Func<string, Func<string, string>> makePrepender = x => y => (x + y); 
Func<string, string> oldPrepend = makePrepender(prepend); 

prepend = "NEW:"; 

Console.WriteLine(oldPrepend("Brownie")); 

Havn't probado todavía, ya que no tengo acceso a VS en este momento, pero normalmente, esto es cómo resuelvo tal problema.

+0

Si bien esto arroja "OLD: Brownie", aún se ha formado un cierre y, por lo tanto, la serialización sigue fallando. –

Cuestiones relacionadas