2009-01-05 19 views
7

Una de las ventajas de las expresiones lambda es que debe evaluar una función solo cuando necesita su resultado.C# expresiones lambda y evaluación diferida

En el ejemplo siguiente (simple), la función de texto sólo se evalúa cuando un escritor está presente:

public static void PrintLine(Func<string> text, TextWriter writer) 
{ 
    if (writer != null) 
    { 
     writer.WriteLine(text()); 
    } 
} 

Desafortunadamente, esto hace que el uso del código un poco feo. No se puede llamar con una constante o variable, como

PrintLine("Some text", Console.Out); 

y tienen que llamar de esta manera:

PrintLine(() => "Some text", Console.Out); 

El compilador no es capaz de "inferir" una función sin parámetros de la constante pasado. ¿Hay algún plan para mejorar esto en versiones futuras de C# o me falta algo?

ACTUALIZACIÓN:

simplemente me encontré con un sucio cortar a mí mismo:

public class F<T> 
    { 
     private readonly T value; 
     private readonly Func<T> func; 

     public F(T value) { this.value = value; } 
     public F(Func<T> func) {this.func = func; } 

     public static implicit operator F<T>(T value) 
     { 
      return new F<T>(value); 
     } 

     public static implicit operator F<T>(Func<T> func) 
     { 
      return new F<T>(func); 
     } 

     public T Eval() 
     { 
      return this.func != null ? this.func() : this.value; 
     } 
} 

Ahora sólo puedo definir la función como:

public static void PrintLine(F<string> text, TextWriter writer) 
{ 
    if (writer != null) 
    { 
     writer.WriteLine(text.Eval()); 
    } 
} 

y lo llaman tanto con una función o una valor.

Respuesta

1

Bueno, esas dos afirmaciones son completamente diferentes. Uno está definiendo una función, mientras que el otro es una declaración. Confundir la sintaxis sería mucho más complicado.

() => "SomeText" //this is a function 

"SomeText" //this is a string 
+0

Estoy de acuerdo. En el caso de agregar una sobrecarga, sería imposible notar la diferencia [si dicho atajo fuera compatible]. – Krisc

3

Usted podría utilizar una sobrecarga: -

public static void PrintLine(string text, TextWriter writer) 
{ 
    PrintLine(() => text, writer); 
} 
+1

Eso frustra el propósito. El argumento sobre la sobrecarga de PrintLine que ha definido se evalúa sin importar qué. Agregar el lambda dentro sirve solo para profundizar innecesariamente la pila de llamadas. –

+0

No solo eso, tienes que crear 2^n sobrecargas para n parámetros. – Rauhotz

+2

Creo que entendí mal las necesidades de OP. Resulta que agregar una sobrecarga en realidad puede ser lo correcto, aunque creo que los invertiría (haciendo que la llamada de un delegado tome la que toma un valor, y no al revés). –

1

Se puede escribir un método de extensión de cadena para pegar en Usted debe ser capaz de escribir "Parte del texto" .PrintLine (Console.Out.); y haz que haga el trabajo por ti.

Por extraño que parezca, he hecho un poco de juego con la evaluación perezosa de expresiones lambda unas pocas semanas atrás y blogged about it here.

+2

En tu blog, estás diciendo que no hay Func . ¿Qué hay de System.Action? – Rauhotz

1

El compilador es muy bueno en la inferencia de tipos , no es bueno para inferir la intención . Una de las cosas complicadas sobre todo el nuevo azúcar sintáctico en C# 3 es que pueden llevar a confusión sobre qué hace exactamente el compilador con ellos.

Tenga en cuenta su ejemplo:

() => "SomeText" 

El compilador ve esto y entiende que tiene la intención de crear una función anónima que no toma parámetros y devuelve un tipo de System.String. Todo esto se infiere de la expresión lambda que le diste. En realidad su lambda se compila a esto:

delegate { 
    return "SomeText"; 
}; 

y es un delegado a esta función anónima que está enviando a PrintLine para su ejecución.

Siempre ha sido importante en el pasado, pero ahora con LINQ, lambdas, bloques iteradores, propiedades implementadas automáticamente, entre otras cosas es de suma importancia usar una herramienta como .NET Reflector para echar un vistazo a su código después de se compila para ver lo que realmente hace que esas funciones funcionen.

3

Dudo que C# obtenga esta característica, pero D la tiene. Lo que ha descrito es una forma adecuada de implementar la evaluación de argumentos perezosos en C#, y probablemente compila de manera muy similar a lazy en D, y en lenguajes funcionales más puros.

Considerando todo, los cuatro caracteres adicionales, más el espacio en blanco opcional, no son un precio excepcionalmente alto para pagar una clara resolución de sobrecarga y expresividad en lo que se está convirtiendo en un lenguaje multi-paradigma de tipo fuerte.

2

Desafortunadamente, la sintaxis fea es todo lo que tiene en C#.

El "truco sucio" de la actualización no funciona, porque no retrasa la evaluación de los parámetros de cadena: se evalúan antes de pasarse a operator F<T>(T value).

Comparar PrintLine(() => string.Join(", ", names), myWriter) a En el primer caso, las cadenas se unen solo si están impresas; en el segundo caso, las cuerdas se unen sin importar qué: solo la impresión es condicional. En otras palabras, la evaluación no es floja en absoluto.