Domanda

Che cos'è una chiusura ? Li abbiamo in .NET?


Se esistono in .NET, potresti fornire uno snippet di codice (preferibilmente in C #) che lo spieghi?


EDIT: ho esaminato l'articolo di Jon Skeet per capire quali sono le chiusure e come usarli in .NET.

È stato utile?

Soluzione

Ho un articolo su questo argomento . (Ha molti esempi.)

In sostanza, una chiusura è un blocco di codice che può essere eseguito in un secondo momento, ma che mantiene l'ambiente in cui è stato creato per la prima volta, ovvero può ancora utilizzare le variabili locali ecc. del metodo che l'ha creato, anche dopo che quel metodo ha terminato l'esecuzione.

La caratteristica generale delle chiusure è implementata in C # con metodi anonimi ed espressioni lambda.

Ecco un esempio usando un metodo anonimo:

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);
        };
    }
}

Output:

counter=1
counter=2

Qui possiamo vedere che l'azione restituita da CreateAction ha ancora accesso alla variabile counter e può effettivamente incrementarla, anche se CreateAction stessa è terminata.

Altri suggerimenti

Se sei interessato a vedere come C # implementa la chiusura leggi " Conosco la risposta (i suoi 42) blog "

Il compilatore genera una classe in background per incapsulare il metodo anoymous e la variabile j

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    public <>c__DisplayClass2();
    public void <fillFunc>b__0()
    {
       Console.Write("{0} ", this.j);
    }
    public int j;
}

per la funzione:

static void fillFunc(int count) {
    for (int i = 0; i < count; i++)
    {
        int j = i;
        funcArr[i] = delegate()
                     {
                         Console.Write("{0} ", j);
                     };
    } 
}

Trasformandolo in:

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);
    }
}

Le chiusure sono valori funzionali che si mantengono su valori variabili dal loro ambito originale. C # può usarli sotto forma di delegati anonimi.

Per un esempio molto semplice, prendi questo codice C #:

    delegate int testDel();

    static void Main(string[] args)
    {
        int foo = 4;
        testDel myClosure = delegate()
        {
            return foo;
        };
        int bar = myClosure();

    }

Al termine, la barra verrà impostata su 4 e il delegato myClosure può essere passato in giro per essere utilizzato altrove nel programma.

Le chiusure possono essere utilizzate per molte cose utili, come l'esecuzione ritardata o per semplificare le interfacce: LINQ è principalmente costruito usando le chiusure. Il modo più immediato in cui è utile per la maggior parte degli sviluppatori è l'aggiunta di gestori di eventi ai controlli creati dinamicamente: puoi usare chiusure per aggiungere comportamento quando il controllo viene istanziato, piuttosto che archiviare dati altrove.

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

Una chiusura è una funzione anonima passata al di fuori della funzione in cui è stata creata. Mantiene tutte le variabili della funzione in cui è stato creato che utilizza.

Ecco un esempio inventato per C # che ho creato da codice simile in 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; };
}

Quindi, ecco un po 'di codice che mostra come usare il codice sopra ...

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

Spero che sia in qualche modo utile.

Fondamentalmente la chiusura è un blocco di codice che puoi passare come argomento a una funzione. C # supporta chiusure sotto forma di delegati anonimi.

Ecco un semplice esempio:
Il metodo List.Find può accettare ed eseguire un pezzo di codice (chiusura) per trovare l'elemento dell'elenco.

// 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 sintassi di C # 3.0 possiamo scrivere questo come:

ints.Find(value => value == 1);

Una chiusura è quando una funzione è definita all'interno di un'altra funzione (o metodo) e utilizza le variabili del metodo genitore . Questo uso di variabili che si trovano in un metodo e racchiuso in una funzione definita al suo interno, è chiamato una chiusura.

Mark Seemann ha alcuni esempi interessanti di chiusure in il suo post sul blog dove fa un parallelo tra oop e programmazione funzionale.

E per renderlo più dettagliato

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.

Le chiusure sono blocchi di codice che fanno riferimento a una variabile al di fuori di se stessi (dal basso nello stack), che potrebbe essere chiamata o eseguita in un secondo momento (come quando viene definito un evento o un delegato e potrebbe essere chiamata in un futuro indefinito point in time) ... Poiché la variabile esterna a cui il blocco di riferimenti di codice potrebbe non rientrare nell'ambito (e sarebbe altrimenti andato perso), il fatto che sia referenziato dal blocco di codice (chiamato chiusura) indica il runtime per "tenere premuto" quella variabile nell'ambito fino a quando non è più necessaria dal blocco di chiusura del codice ...

Ho cercato anche di capirlo, ben sotto ci sono i frammenti di codice per lo stesso codice in Javascript e C # che mostra la chiusura.

  1. Numero di volte in cui si è verificato ogni evento o numero di volte in cui è stato fatto clic su ciascun pulsante

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);
    }
}
  1. conta il numero totale di volte in cui si è verificato un evento clic o conta il numero totale di clic indipendentemente dal controllo.

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);
    }
}

Appena fuori dal nulla, una risposta semplice e comprensiva dal libro C # 7.0 in breve.

Prerequisito che dovresti conoscere : un'espressione lambda può fare riferimento alle variabili e ai parametri locali del metodo in cui è definito (variabili esterne).

    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 reale : le variabili esterne a cui fa riferimento un'espressione lambda sono chiamate variabili acquisite. Un'espressione lambda che cattura le variabili è chiamata chiusura.

Ultimo punto da notare : le variabili acquisite vengono valutate quando il delegato viene effettivamente richiamato, non quando le variabili sono state acquisite:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30

Se si scrive un metodo anonimo in linea (C # 2) o (preferibilmente) un'espressione Lambda (C # 3 +), viene ancora creato un metodo effettivo. Se quel codice utilizza una variabile locale di ambito esterno, è comunque necessario in qualche modo passare quella variabile al metodo.

es. prendi questa clausola Linq Where (che è un semplice metodo di estensione che passa un'espressione 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;
});

se vuoi usare i in quell'espressione lambda, devi passarlo a quel metodo creato.

Quindi la prima domanda che si pone è: dovrebbe essere passato per valore o riferimento?

Il passaggio per riferimento è (immagino) più preferibile quando ottieni l'accesso in lettura / scrittura a quella variabile (e questo è ciò che fa C #; immagino che il team di Microsoft abbia valutato i pro e i contro e abbia seguito il riferimento; Secondo a l'articolo di Jon Skeet , Java è andato per valore).

Ma poi sorge un'altra domanda: Dove assegnare che io?

Dovrebbe essere effettivamente / naturalmente allocato nello stack? Bene, se lo allocate nello stack e lo passate per riferimento, ci possono essere situazioni in cui sopravvive al proprio frame dello stack. Prendi questo esempio:

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;
    });            
}

L'espressione lambda (nella clausola Where) crea nuovamente un metodo che fa riferimento a una i. Se i è allocato sullo stack di Outlive, quando si enumerano whereItems, l'i utilizzato nel metodo generato punterà all'i di Outlive, ovvero a un punto dello stack che non è più accessibile.

Ok, allora ne abbiamo bisogno sull'heap.

Quindi, ciò che il compilatore C # fa per supportare questo inline anonymous / lambda, è usare quello che viene chiamato " Chiusure " ;: Crea una classe sull'heap chiamata ( piuttosto male ) DisplayClass che ha un campo contenente l'i e la funzione che lo utilizza effettivamente.

Qualcosa che sarebbe equivalente a questo (puoi vedere l'IL generato usando ILSpy o ILDASM):

class <>c_DisplayClass1
{
    public int i;

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

Crea un'istanza di quella classe nell'ambito locale e sostituisce qualsiasi codice relativo a i o all'espressione lambda con quell'istanza di chiusura. Quindi - ogni volta che stai usando l'i nel tuo "ambito locale" codice in cui sono stato definito, stai effettivamente utilizzando quel campo di istanza DisplayClass.

Quindi, se cambiassi il " local " i nel metodo principale, cambierà effettivamente _DisplayClass.i;

vale a dire.

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

stamperà 12, come " i = 10 " va in quel campo di dispalyclass e lo cambia poco prima della seconda enumerazione.

Una buona fonte sull'argomento è questa modulo Bart De Smet Pluralsight (richiede la registrazione) (ignora anche il suo uso errato del termine " Hoisting " - cosa (penso) intende che è la variabile locale (es. i) viene modificato per fare riferimento al nuovo campo DisplayClass).


In altre notizie, sembra esserci un malinteso secondo cui "chiusure" sono correlati ai loop - a quanto ho capito " Chiusure " NON sono un concetto correlato ai loop , ma piuttosto a metodi anonimi / espressioni lambda che utilizzano variabili con ambito locale, anche se alcune domande trabocchetto utilizzano i loop per dimostrarlo.

Una chiusura è una funzione, definita all'interno di una funzione, che può accedere alle variabili locali di essa e al suo genitore.

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

quindi la funzione all'interno del metodo find.

 t => t.Name == name

può accedere alle variabili all'interno del suo ambito, t, e al nome della variabile che si trova nel suo ambito padre. Anche se viene eseguito dal metodo find come delegato, da un altro ambito tutti insieme.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top