¿Qué son ‘cierres’ en .NET?

¿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?

EDITAR: revisé el artículo de Jon Skeet para entender qué son los cierres y cómo usarlos en .NET.

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 más adelante, pero que mantiene el entorno en el que se creó por primera vez, es decir, 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 aún tiene acceso a la variable de contador, y de hecho puede incrementarla, incluso aunque CreateAction haya finalizado.

Si está interesado en ver cómo C # implementa Cierre, lea “Conozco la respuesta (es 42) blog”

El comstackdor 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 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.b__0); } } 

Los cierres son valores funcionales que se mantienen en valores variables desde su scope original. C # puede usarlos en forma de delegates 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 otro lugar del progtwig.

Los cierres se pueden utilizar para muchas cosas útiles, como la ejecución retrasada o para simplificar las interfaces: LINQ se crea principalmente mediante cierres. La manera más inmediata que resulta útil para la mayoría de los desarrolladores es agregar manejadores de eventos a controles creados dinámicamente; puede usar cierres para agregar comportamiento cuando se crea una instancia del control, en lugar de almacenar datos en otro lugar.

 Func 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 que se pasa fuera de la función en la que se creó. Mantiene todas las variables de la función en la que se creó que utiliza.

Aquí hay un ejemplo inventado para C # que he creado a partir de código similar en JavaScript:

 public delegate T Iterator() where T : class; public Iterator CreateIterator(IList 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 manera útil.

Básicamente, el cierre es un bloque de código que puede pasar como argumento para una función. C # admite cierres en forma de delegates 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 ints = new List {1, 2, 3}; ints.Find(delegate(int value) { return value == 1; }); 

Usando la syntax 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 llama cierre.

Mark Seemann tiene algunos ejemplos interesantes de cierres en su publicación de blog donde hace un paralelismo entre oop y progtwigción funcional.

Y para hacerlo más detallado

 var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable Func 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 stack), que podría llamarse o ejecutarse más tarde (como cuando se define un evento o delegado y podría llamarse en algún punto futuro indefinido en el tiempo) ) … Debido a que la variable externa que el fragmento de referencias de código puede haber salido del scope (y que de otro modo se habría perdido), el hecho de que esté referenciado por el fragmento de código (llamado cierre) le dice al tiempo de ejecución que “mantenga” “esa variable en el scope hasta que ya no sea necesaria por el fragmento de código de cierre …

También he intentado entenderlo, muy abajo están los fragmentos de código para el mismo código en Javascript y C # mostrando el cierre.

  1. Recuento No de veces cada evento ha sucedido o ninguna de las veces que se hace clic en cada botón.

JavaScript:

 var c = function () { var d = 0; function inner() { d++; alert(d); } return inner; }; var a = c(); var b = c();     

DO#:

 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); } } 
  1. recuento Total de veces que ha pasado el evento de clic o cuenta total de clics sin consideración de control.

JavaScript:

 var c = function () { var d = 0; function inner() { d++; alert(d); } return inner; }; var a = c();   

DO#:

 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 en pocas palabras.

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 está definido (variables externas).

  static void Main() { int factor = 2; //Here factor is the variable that takes part in lambda expression. Func multiplier = n => n * factor; Console.WriteLine (multiplier (3)); // 6 } 

Parte real : las variables externas a las que hace referencia una expresión lambda se llaman variables capturadas. Una expresión lambda que captura variables se llama cierre.

Último punto que se debe tener en cuenta : las variables capturadas se evalúan cuando el delegado se invoca realmente, no cuando se capturaron las variables:

 int factor = 2; Func 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 scope externo, aún debe pasar esa variable al método de alguna manera.

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 { "Hello","World" }; var filtered = items.Where(x => // this is a predicate, ie a Func written as a lambda expression // which is still a method actually being created for you in compile time { i++; return true; }); 

si quieres usar i en esa expresión lambda, debes pasarlo a ese método creado.

Entonces, la primera pregunta que surge es: ¿ debería pasarse 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 se fue con referencia; De acuerdo con Jon Skeet artículo , Java fue con valor por).

Pero luego surge otra pregunta: ¿dónde asignar eso?

¿Debería asignarse realmente / naturalmente en la stack? Bueno, si lo asigna a la stack y lo pasa por referencia, puede haber situaciones en las que sobreviva a su propio marco de stack. Toma este ejemplo:

 static void Main(string[] args) { Outlive(); var list = whereItems.ToList(); Console.ReadLine(); } static IEnumerable whereItems; static void Outlive() { var i = 0; var items = new List { "Hello","World" }; whereItems = items.Where(x => { i++; Console.WriteLine(i); return true; }); } 

La expresión lambda (en la cláusula Where) crea nuevamente un método que se refiere a un i. Si me asignan en la stack de Outlive, entonces en el momento en que enumere whereItems, el i usado en el método generado apuntará a la i de Outlive, es decir, a un lugar en la stack que ya no es accesible.

Ok, entonces lo necesitamos en el montón entonces.

Entonces, ¿qué hace el comstackdor C # para soportar este anónimo / lambda en línea? Se usa lo que se llama ” Cierres “: crea una clase en el Heap llamada ( bastante pobre ) 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 el IL generado usando ILSpy o ILDASM):

 class <>c_DisplayClass1 { public int i; public bool b__0() { this.i++; Console.WriteLine(i); return true; } } 

Instala esa clase en su ámbito local y reemplaza cualquier código relacionado con i o la expresión lambda con esa instancia de cierre. Entonces, cada vez que use i en su código de “scope local” donde me definieron, en realidad está usando ese campo de instancia de DisplayClass.

Entonces, si cambio el “local” i en el método principal, en realidad cambiará _DisplayClass.i;

es decir

 var i = 0; var items = new List { "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 pantalla y lo cambia justo antes de la segunda enumeración.

Una buena fuente sobre el tema es este módulo Pluralsight de Bart De Smet (requiere registro) (también ignore su uso erróneo del término “elevación”; lo que (creo) 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 bucles, ya que entiendo que “cierres” NO son un concepto relacionado con bucles , sino más bien con métodos anónimos / expresiones lambda uso de variables de ámbito local, aunque algún truco 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, así como a su elemento principal.

 public string GetByName(string name) { List theThings = new List(); return theThings.Find(t => t.Name == name)[0]; } 

por lo que la función dentro del método de búsqueda.

  t => t.Name == name 

puede acceder a las variables dentro de su scope, t, y al nombre de la variable que está en el scope de sus padres. Aunque el método de búsqueda lo ejecuta como un delegado, desde otro ámbito todos juntos.