¿Qué es un cierre? ? ¿Los tenemos en .NET?
Si existen en .NET, ¿podría proporcionar un fragmento de código (preferiblemente en C #) que lo explique?
¿Qué es un cierre? ? ¿Los tenemos en .NET?
Si existen en .NET, ¿podría proporcionar un fragmento de código (preferiblemente en C #) que lo explique?
Respuestas:
Tengo un artículo sobre este mismo tema . (Tiene muchos ejemplos).
En esencia, un cierre es un bloque de código que se puede ejecutar en un momento posterior, pero que mantiene el entorno en el que se creó por primera vez, es decir, todavía puede usar las variables locales, etc. del método que lo creó, incluso después de eso. El método ha terminado de ejecutarse.
La característica general de los cierres se implementa en C # mediante métodos anónimos y expresiones lambda.
Aquí hay un ejemplo usando un método anónimo:
using System;
class Test
{
static void Main()
{
Action action = CreateAction();
action();
action();
}
static Action CreateAction()
{
int counter = 0;
return delegate
{
// Yes, it could be done in one statement;
// but it is clearer like this.
counter++;
Console.WriteLine("counter={0}", counter);
};
}
}
Salida:
counter=1
counter=2
Aquí podemos ver que la acción devuelta por CreateAction todavía tiene acceso a la variable del contador y, de hecho, puede incrementarla, aunque CreateAction haya finalizado.
counter
está disponible para ser incrementado: el compilador genera una clase que contiene un counter
campo, y cualquier código que haga referencia counter
termina en una instancia de esa clase.
Si está interesado en ver cómo C # implementa Closure, lea "Sé la respuesta (es 42) blog"
El compilador genera una clase en segundo plano para encapsular el método anónimo y la variable j
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
para la función:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Convirtiéndolo en:
private static void fillFunc(int count)
{
for (int i = 0; i < count; i++)
{
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
class1.j = i;
Program.funcArr[i] = new Func(class1.<fillFunc>b__0);
}
}
Los cierres son valores funcionales que mantienen valores variables de su alcance original. C # puede usarlos en forma de delegados anónimos.
Para un ejemplo muy simple, tome este código C #:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
Al final, la barra se establecerá en 4, y el delegado de myClosure se puede pasar para usarlo en otra parte del programa.
Los cierres se pueden usar para muchas cosas útiles, como la ejecución retrasada o para simplificar las interfaces: LINQ se construye principalmente con cierres. La forma más inmediata que resulta útil para la mayoría de los desarrolladores es agregar controladores de eventos a los controles creados dinámicamente: puede usar cierres para agregar comportamiento cuando se instancia el control, en lugar de almacenar datos en otro lugar.
Func<int, int> GetMultiplier(int a)
{
return delegate(int b) { return a * b; } ;
}
//...
var fn2 = GetMultiplier(2);
var fn3 = GetMultiplier(3);
Console.WriteLine(fn2(2)); //outputs 4
Console.WriteLine(fn2(3)); //outputs 6
Console.WriteLine(fn3(2)); //outputs 6
Console.WriteLine(fn3(3)); //outputs 9
Un cierre es una función anónima pasada fuera de la función en la que se crea. Mantiene cualquier variable de la función en la que se crea que utiliza.
Aquí hay un ejemplo artificial para C # que creé a partir de un código similar en JavaScript:
public delegate T Iterator<T>() where T : class;
public Iterator<T> CreateIterator<T>(IList<T> x) where T : class
{
var i = 0;
return delegate { return (i < x.Count) ? x[i++] : null; };
}
Entonces, aquí hay un código que muestra cómo usar el código anterior ...
var iterator = CreateIterator(new string[3] { "Foo", "Bar", "Baz"});
// So, although CreateIterator() has been called and returned, the variable
// "i" within CreateIterator() will live on because of a closure created
// within that method, so that every time the anonymous delegate returned
// from it is called (by calling iterator()) it's value will increment.
string currentString;
currentString = iterator(); // currentString is now "Foo"
currentString = iterator(); // currentString is now "Bar"
currentString = iterator(); // currentString is now "Baz"
currentString = iterator(); // currentString is now null
Espero que sea de alguna ayuda.
Básicamente, el cierre es un bloque de código que puede pasar como argumento a una función. C # admite cierres en forma de delegados anónimos.
Aquí hay un ejemplo simple: el
método List.Find puede aceptar y ejecutar un fragmento de código (cierre) para encontrar el elemento de la lista.
// Passing a block of code as a function argument
List<int> ints = new List<int> {1, 2, 3};
ints.Find(delegate(int value) { return value == 1; });
Usando la sintaxis de C # 3.0 podemos escribir esto como:
ints.Find(value => value == 1);
Un cierre es cuando una función se define dentro de otra función (o método) y utiliza las variables del método principal . Este uso de variables que se ubican en un método y se envuelven en una función definida dentro de él, se denomina cierre.
Mark Seemann tiene algunos ejemplos interesantes de cierres en su publicación de blog donde hace un paralelismo entre la programación funcional y oop.
Y para hacerlo más detallado
var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable
Func<int, string> read = id =>
{
var path = Path.Combine(workingDirectory.FullName, id + ".txt");//is used inside this function
return File.ReadAllText(path);
};//the entire process is called a closure.
Los cierres son fragmentos de código que hacen referencia a una variable fuera de ellos (desde debajo de ellos en la pila), que podrían llamarse o ejecutarse más tarde (como cuando se define un evento o delegado, y podrían llamarse en algún momento futuro indefinido) ) ... Debido a que la variable externa a la que el fragmento de código hace referencia puede estar fuera de alcance (y de lo contrario se habría perdido), el hecho de que se hace referencia al fragmento de código (llamado cierre) le dice al tiempo de ejecución que "retenga "esa variable en alcance hasta que ya no sea necesaria por el fragmento de código de cierre ...
También he intentado entenderlo, a continuación se encuentran los fragmentos de código para el mismo código en Javascript y C # que muestran el cierre.
JavaScript:
var c = function ()
{
var d = 0;
function inner() {
d++;
alert(d);
}
return inner;
};
var a = c();
var b = c();
<body>
<input type=button value=call onClick="a()"/>
<input type=button value=call onClick="b()"/>
</body>
C#:
using System.IO;
using System;
class Program
{
static void Main()
{
var a = new a();
var b = new a();
a.call();
a.call();
a.call();
b.call();
b.call();
b.call();
}
}
public class a {
int b = 0;
public void call()
{
b++;
Console.WriteLine(b);
}
}
JavaScript:
var c = function ()
{
var d = 0;
function inner() {
d++;
alert(d);
}
return inner;
};
var a = c();
<input type=button value=call onClick="a()"/>
<input type=button value=call onClick="a()"/>
C#:
using System.IO;
using System;
class Program
{
static void Main()
{
var a = new a();
var b = new a();
a.call();
a.call();
a.call();
b.call();
b.call();
b.call();
}
}
public class a {
static int b = 0;
public void call()
{
b++;
Console.WriteLine(b);
}
}
De la nada, una respuesta simple y más comprensiva del libro C # 7.0.
Requisito previo que debe saber : una expresión lambda puede hacer referencia a las variables y parámetros locales del método en el que se define (variables externas).
static void Main()
{
int factor = 2;
//Here factor is the variable that takes part in lambda expression.
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}
Parte real : las variables externas a las que hace referencia una expresión lambda se denominan variables capturadas. Una expresión lambda que captura variables se llama cierre.
Último punto a tener en cuenta : las variables capturadas se evalúan cuando se invoca al delegado, no cuando se capturan las variables:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Si escribe un método anónimo en línea (C # 2) o (preferiblemente) una expresión Lambda (C # 3 +), todavía se está creando un método real. Si ese código está usando una variable local de alcance externo, de todos modos necesita pasar esa variable al método.
por ejemplo, tome esta cláusula Linq Where (que es un método de extensión simple que pasa una expresión lambda):
var i = 0;
var items = new List<string>
{
"Hello","World"
};
var filtered = items.Where(x =>
// this is a predicate, i.e. a Func<T, bool> written as a lambda expression
// which is still a method actually being created for you in compile time
{
i++;
return true;
});
si desea usar i en esa expresión lambda, debe pasarlo a ese método creado.
Entonces, la primera pregunta que surge es: ¿se debe pasar por valor o referencia?
Pasar por referencia es (supongo) más preferible a medida que obtienes acceso de lectura / escritura a esa variable (y esto es lo que hace C #; supongo que el equipo de Microsoft sopesó los pros y los contras y fue con referencia; según Jon Skeet's artículo , Java fue con by-value).
Pero luego surge otra pregunta: ¿Dónde asignar ese yo?
¿Debería asignarse realmente / naturalmente en la pila? Bueno, si lo asigna en la pila y lo pasa por referencia, puede haber situaciones en las que sobreviva su propio marco de pila. Toma este ejemplo:
static void Main(string[] args)
{
Outlive();
var list = whereItems.ToList();
Console.ReadLine();
}
static IEnumerable<string> whereItems;
static void Outlive()
{
var i = 0;
var items = new List<string>
{
"Hello","World"
};
whereItems = items.Where(x =>
{
i++;
Console.WriteLine(i);
return true;
});
}
La expresión lambda (en la cláusula Where) nuevamente crea un método que se refiere a una i. Si i está asignado en la pila de Outlive, para cuando enumere whereItems, el i utilizado en el método generado apuntará a i de Outlive, es decir, a un lugar en la pila que ya no es accesible.
Ok, entonces lo necesitamos en el montón entonces.
Entonces, lo que el compilador de C # hace para admitir este anónimo / lambda en línea es usar lo que se llama " Closures ": crea una clase en el montón llamada ( bastante mal DisplayClass ) que tiene un campo que contiene el i, y la función que realmente usa eso.
Algo que sería equivalente a esto (puede ver la IL generada usando ILSpy o ILDASM):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
Crea una instancia de esa clase en su ámbito local y reemplaza cualquier código relacionado con yo o la expresión lambda con esa instancia de cierre. Entonces, cada vez que use la i en su código de "ámbito local" donde estaba definida, en realidad está usando ese campo de instancia DisplayClass.
Entonces, si cambiara el "local" i en el método principal, en realidad cambiará _DisplayClass.i;
es decir
var i = 0;
var items = new List<string>
{
"Hello","World"
};
var filtered = items.Where(x =>
{
i++;
return true;
});
filtered.ToList(); // will enumerate filtered, i = 2
i = 10; // i will be overwriten with 10
filtered.ToList(); // will enumerate filtered again, i = 12
Console.WriteLine(i); // should print out 12
imprimirá 12, ya que "i = 10" va a ese campo de clase de muestra y lo cambia justo antes de la segunda enumeración.
Una buena fuente sobre el tema es este módulo de Bart De Smet Pluralsight (requiere registro) (también ignora su uso erróneo del término "elevación" - lo que (creo) que quiere decir es que la variable local (es decir, i) se cambia para referirse al nuevo campo DisplayClass).
En otras noticias, parece haber una idea errónea de que los "cierres" están relacionados con los bucles, ya que entiendo que los "cierres" NO son un concepto relacionado con los bucles , sino más bien con métodos anónimos / expresiones lambda que utilizan variables de ámbito local, aunque algunos trucos las preguntas usan bucles para demostrarlo.
Un cierre es una función, definida dentro de una función, que puede acceder a las variables locales de la misma, así como a su padre.
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
entonces la función dentro del método find.
t => t.Name == name
puede acceder a las variables dentro de su alcance, t, y el nombre de la variable que está en su alcance primario. Aunque el método find lo ejecuta como delegado, desde otro ámbito, todos juntos.