c# - sharp - Explicación detallada de la captura variable en cierres
reference type c# (1)
He visto innumerables publicaciones sobre la forma en que la captura de variables extrae variables para la creación del cierre, sin embargo, parece que no llegan a detalles específicos y lo llaman "magia del compilador".
Estoy buscando una explicación clara de:
- Cómo se capturan las variables locales
- La diferencia (si existe) entre los tipos de valores de captura frente a los tipos de referencia.
- Y si hay algún boxeo ocurriendo con respecto a los tipos de valores.
Mi preferencia sería una respuesta en términos de valores y sugerencias (más cerca del corazón de lo que ocurre internamente), aunque también aceptaré una respuesta clara que incluya valores y referencias.
- Es complicado Entrará en eso en un minuto.
- No hay diferencia: en ambos casos, es la variable en sí la que se captura.
- No, no hay boxeo.
Probablemente sea más fácil demostrar cómo funciona la captura a través de un ejemplo ...
Aquí hay un código que usa una expresión lambda que captura una sola variable:
using System;
class Test
{
static void Main()
{
Action action = CreateShowAndIncrementAction();
action();
action();
}
static Action CreateShowAndIncrementAction()
{
Random rng = new Random();
int counter = rng.Next(10);
Console.WriteLine("Initial value for counter: {0}", counter);
return () =>
{
Console.WriteLine(counter);
counter++;
};
}
}
Ahora esto es lo que el compilador está haciendo por ti, excepto que usaría nombres "indescriptibles" que realmente no podrían ocurrir en C #.
using System;
class Test
{
static void Main()
{
Action action = CreateShowAndIncrementAction();
action();
action();
}
static Action CreateShowAndIncrementAction()
{
ActionHelper helper = new ActionHelper();
Random rng = new Random();
helper.counter = rng.Next(10);
Console.WriteLine("Initial value for counter: {0}", helper.counter);
// Converts method group to a delegate, whose target will be a
// reference to the instance of ActionHelper
return helper.DoAction;
}
class ActionHelper
{
// Just for simplicity, make it public. I don''t know if the
// C# compiler really does.
public int counter;
public void DoAction()
{
Console.WriteLine(counter);
counter++;
}
}
}
Si captura las variables declaradas en un bucle, terminaría con una nueva instancia de ActionHelper
para cada iteración del ciclo, por lo que efectivamente capturaría diferentes "instancias" de las variables.
Se vuelve más complicado cuando captura variables desde diferentes ámbitos ... hágame saber si realmente desea ese tipo de nivel de detalle, o podría simplemente escribir un código, descompilarlo en Reflector y seguirlo :)
Tenga en cuenta cómo:
- No hay boxeo involucrado
- No hay punteros involucrados, ni ningún otro código inseguro
EDITAR: Aquí hay un ejemplo de dos delegados que comparten una variable. Un delegado muestra el valor actual del counter
, el otro lo incrementa:
using System;
class Program
{
static void Main(string[] args)
{
var tuple = CreateShowAndIncrementActions();
var show = tuple.Item1;
var increment = tuple.Item2;
show(); // Prints 0
show(); // Still prints 0
increment();
show(); // Now prints 1
}
static Tuple<Action, Action> CreateShowAndIncrementActions()
{
int counter = 0;
Action show = () => { Console.WriteLine(counter); };
Action increment = () => { counter++; };
return Tuple.Create(show, increment);
}
}
... y la expansión:
using System;
class Program
{
static void Main(string[] args)
{
var tuple = CreateShowAndIncrementActions();
var show = tuple.Item1;
var increment = tuple.Item2;
show(); // Prints 0
show(); // Still prints 0
increment();
show(); // Now prints 1
}
static Tuple<Action, Action> CreateShowAndIncrementActions()
{
ActionHelper helper = new ActionHelper();
helper.counter = 0;
Action show = helper.Show;
Action increment = helper.Increment;
return Tuple.Create(show, increment);
}
class ActionHelper
{
public int counter;
public void Show()
{
Console.WriteLine(counter);
}
public void Increment()
{
counter++;
}
}
}