Las cadenas son tipos de referencia, pero son inmutables. Esto les permite ser internados por el compilador; en todas partes aparece el mismo literal de cadena, el mismo objeto puede ser referenciado.¿Por qué las expresiones lambda no son "internas"?
Los delegados también son tipos de referencia inmutables. (Agregar un método a un delegado de multidifusión usando el operador +=
constituye asignación; eso no es mutabilidad). Y, como cadenas, hay una forma "literal" de representar a un delegado en el código, usando una expresión lambda, por ejemplo:
Func<int> func =() => 5;
El lado derecho de esa declaración es una expresión cuyo tipo es Func<int>
; pero en ninguna parte estoy invocando explícitamente el constructor Func<int>
(ni está ocurriendo una conversión implícita). Así que veo esto como esencialmente un literal. ¿Estoy equivocado sobre mi definición de "literal" aquí?
De todos modos, aquí está mi pregunta. Si tengo dos variables para, por ejemplo, el tipo Func<int>
, y asigno expresiones lambda idénticos a ambos:
Func<int> x =() => 5;
Func<int> y =() => 5;
... lo que está impidiendo el compilador de tratar a estos como el mismo Func<int>
objeto?
Lo pregunto porque la sección 6.5.1 de la C# 4.0 language specification indica claramente:
Conversiones de semánticamente idénticos funciones anónimas con el mismo conjunto (posiblemente vacío) de instancias de variables externas capturados a la misma delegado tipos están permitidos (pero no se requiere ) para devolver la misma instancia delegado . El término semánticamente idéntico se utiliza aquí para indicar que la ejecución de las funciones anónimas producirá, en todos los casos, los mismos efectos dados los mismos argumentos.
Esto me sorprendió cuando lo leí; si este comportamiento es explícitamente permitido, habría esperado que se implementara. Pero parece que no lo es. Esto de hecho ha metido a muchos desarrolladores en problemas, esp. cuando las expresiones lambda se han utilizado para adjuntar controladores de eventos con éxito sin poder eliminarlos. Por ejemplo:
class EventSender
{
public event EventHandler Event;
public void Send()
{
EventHandler handler = this.Event;
if (handler != null) { handler(this, EventArgs.Empty); }
}
}
class Program
{
static string _message = "Hello, world!";
static void Main()
{
var sender = new EventSender();
sender.Event += (obj, args) => Console.WriteLine(_message);
sender.Send();
// Unless I'm mistaken, this lambda expression is semantically identical
// to the one above. However, the handler is not removed, indicating
// that a different delegate instance is constructed.
sender.Event -= (obj, args) => Console.WriteLine(_message);
// This prints "Hello, world!" again.
sender.Send();
}
}
¿Hay alguna razón por la cual este comportamiento, una instancia de delegado para anónimas semánticamente idénticos métodos, no ha aplicado?
Incluso si funcionó, todavía creo que es una mala idea separar un controlador de eventos duplicando el código en el lambda. –
Sospecho que este será otro de esos "porque el presupuesto no estaba disponible para diseñar, implementar, probar, documentar, enviar y mantener" agradables para tener. Tal vez Eric Lippert pueda sonar con información privilegiada. – LukeH
Buena pregunta. Yo postularía que una lambda como se ve en el código puede parecer idéntica a otra, pero debido a las diferencias referenciales como los cierres externos no se compilaría en el mismo MSIL en ambos lugares. El compilador tiene que compilarlo en MSIL para detectar la diferencia, en lugar de poder detectar un literal de cadena "sobre la marcha" desde el código fuente. Como este es un paso adicional que se requeriría para cualquier lambda, y solo proporciona un ahorro de tamaño pequeño y poco o ningún aumento en el rendimiento, probablemente se lo saltaron. – KeithS