c# - metodos - Alcance de variables en un delegado
funcion delegado c# (6)
Encontré lo siguiente bastante extraño. Por otra parte, he usado principalmente cierres en lenguajes dinámicos que no deberían ser sospechosos del mismo "error". Lo siguiente hace que el compilador no esté satisfecho:
VoidFunction t = delegate { int i = 0; };
int i = 1;
Dice:
Una variable local llamada ''i'' no se puede declarar en este ámbito porque daría un significado diferente a ''i'', que ya se usa en un alcance ''secundario'' para denotar algo más
Así que esto básicamente significa que las variables declaradas dentro de un delegado tendrán el alcance de la función declarada. No es exactamente lo que esperaba. Ni siquiera he intentado llamar a la función. Al menos Common Lisp tiene una función en la que dices que una variable debe tener un nombre dinámico, si realmente quieres que sea local. Esto es particularmente importante cuando se crean macros que no tienen fugas, pero algo así sería útil aquí también.
Entonces, me pregunto qué hacen otras personas para solucionar este problema.
Para aclararlo, estoy buscando una solución donde las variables que declaro en el delegete no interfieran con las variables declaradas después del delegado. Y aún quiero poder capturar las variables declaradas antes del delegado.
El "cierre" creado por una función anónima es algo diferente de la creada en otros lenguajes dinámicos (usaré Javascript como ejemplo).
function thing() {
var o1 = {n:1}
var o2 = {dummy:"Hello"}
return function() { return o1.n++; }
}
var fn = thing();
alert(fn());
alert(fn());
Este pequeño fragmento de javascript mostrará 1 y luego 2. La función anónima puede acceder a la variable o1 porque existe en su cadena de alcance. Sin embargo, la función anónima tiene un alcance totalmente independiente en el que podría crear otra variable o1 y, por lo tanto, ocultar cualquier otro más abajo en la cadena del alcance. Tenga en cuenta también que todas las variables en toda la cadena permanecen, por lo tanto o2 continuaría existiendo sosteniendo una referencia de objeto mientras fn varialbe tenga la referencia de función.
Ahora compare con las funciones anónimas de C #:
class C1 { public int n {get; set;} }
class C2 { public string dummy { get; set; } }
Func<int> thing() {
var o1 = new C1() {n=1};
var o2 = new C2() {dummy="Hello"};
return delegate { return o1.n++; };
}
...
Func<int> fn = thing();
Console.WriteLine(fn());
Console.WriteLine(fn());
En este caso, la función anónima no está creando un alcance verdaderamente independiente, de la misma forma que la declaración de variables en cualquier otro bloque de código en función {} (se usa en un foreach
, if
, etc.)
Por lo tanto, se aplican las mismas reglas, el código fuera del bloque no puede acceder a las variables declaradas dentro del bloque, pero tampoco puede reutilizar un identificador.
Se crea un cierre cuando la función anónima se pasa fuera de la función en la que se creó. La variación del ejemplo de JavaScript es que solo permanecerán las variables realmente utilizadas por la función anónima, por lo tanto, en este caso, el objeto mantenido por o2 estar disponible para GC tan pronto como la cosa se complete,
En realidad, el error no parece tener nada que ver con los delegados anónimos o las expresiones lamda. Si intenta compilar el siguiente programa ...
using System;
class Program
{
static void Main()
{
// Action t = delegate
{
int i = 0;
};
int i = 1;
}
}
... obtienes exactamente el mismo error, sin importar si comentas en la línea o no. La ayuda de error muestra un caso muy similar. Creo que es razonable desestimar ambos casos con el argumento de que los programadores podrían confundir las dos variables.
Es porque el delegado puede referenciar variables fuera del delegado:
int i = 1;
VoidFunction t = delegate { Console.WriteLine(i); };
Si recuerdo correctamente, el compilador crea un miembro de la clase de las variables externas a las que se hace referencia en el método anónimo, para que esto funcione.
Aquí hay una solución:
class Program
{
void Main()
{
VoidFunction t = RealFunction;
int i = 1;
}
delegate void VoidFunction();
void RealFunction() { int i = 0; }
}
También obtendrás CS0136 de un código como este:
int i = 0;
if (i == 0) {
int i = 1;
}
El alcance de la 2ª declaración de "i" no es ambiguo, los lenguajes como C ++ no tienen nada que ver con eso. Pero los diseñadores del lenguaje C # decidieron prohibirlo. Dado el fragmento de arriba, ¿crees que todavía piensas que fue una mala idea? Agregue un montón de código adicional y podría mirar este código por un tiempo y no ver el error.
La solución es trivial e indolora, solo proponga un nombre de variable diferente.
Tiene que ser de esa manera para permitir que los métodos anónimos (y lambdas) usen variables locales y parámetros delimitados en el método contenedor.
Las soluciones alternativas son usar nombres diferentes para la variable o crear un método ordinario.