Qu'est-ce qu'une fermeture ? Les avons-nous en .NET?
S'ils existent dans .NET, pourriez-vous s'il vous plaît fournir un extrait de code (de préférence en C #) l'expliquant?
Qu'est-ce qu'une fermeture ? Les avons-nous en .NET?
S'ils existent dans .NET, pourriez-vous s'il vous plaît fournir un extrait de code (de préférence en C #) l'expliquant?
Réponses:
J'ai un article sur ce même sujet . (Il contient de nombreux exemples.)
En substance, une fermeture est un bloc de code qui peut être exécuté ultérieurement, mais qui maintient l'environnement dans lequel il a été créé pour la première fois - c'est-à-dire qu'il peut toujours utiliser les variables locales, etc. de la méthode qui l'a créé, même après cela. La méthode a terminé son exécution.
La fonctionnalité générale des fermetures est implémentée en C # par des méthodes anonymes et des expressions lambda.
Voici un exemple utilisant une méthode anonyme:
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);
};
}
}
Production:
counter=1
counter=2
Ici, nous pouvons voir que l'action retournée par CreateAction a toujours accès à la variable compteur, et peut en effet l'incrémenter, même si CreateAction elle-même est terminée.
counter
est disponible pour être incrémenté - le compilateur génère une classe qui contient un counter
champ, et tout code faisant référence counter
finit par passer par une instance de cette classe.
Si vous souhaitez voir comment C # implémente Closure, lisez "Je connais la réponse (son 42) blog"
Le compilateur génère une classe en arrière-plan pour encapsuler la méthode anoyme et 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;
}
pour la fonction:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Le transformer 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);
}
}
Les fermetures sont des valeurs fonctionnelles qui conservent des valeurs variables de leur portée d'origine. C # peut les utiliser sous forme de délégués anonymes.
Pour un exemple très simple, prenez ce code C #:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
À la fin de celui-ci, la barre sera définie sur 4 et le délégué myClosure peut être transmis pour être utilisé ailleurs dans le programme.
Les fermetures peuvent être utilisées pour de nombreuses choses utiles, comme l'exécution retardée ou pour simplifier les interfaces - LINQ est principalement construit à l'aide de fermetures. Pour la plupart des développeurs, la manière la plus immédiate qui soit utile consiste à ajouter des gestionnaires d'événements aux contrôles créés dynamiquement - vous pouvez utiliser des fermetures pour ajouter un comportement lorsque le contrôle est instancié, plutôt que de stocker des données ailleurs.
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
Une fermeture est une fonction anonyme passée en dehors de la fonction dans laquelle elle est créée. Il conserve toutes les variables de la fonction dans laquelle il est créé qu'il utilise.
Voici un exemple artificiel pour C # que j'ai créé à partir d'un code similaire 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; };
}
Voici donc du code qui montre comment utiliser le code ci-dessus ...
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
J'espère que cela est quelque peu utile.
Fondamentalement, la fermeture est un bloc de code que vous pouvez passer comme argument à une fonction. C # prend en charge les fermetures sous forme de délégués anonymes.
Voici un exemple simple:
La méthode List.Find peut accepter et exécuter un morceau de code (fermeture) pour rechercher l'élément de la liste.
// 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; });
En utilisant la syntaxe C # 3.0, nous pouvons écrire ceci comme:
ints.Find(value => value == 1);
Une fermeture se produit lorsqu'une fonction est définie à l'intérieur d'une autre fonction (ou méthode) et qu'elle utilise les variables de la méthode parente . Cette utilisation de variables qui sont situées dans une méthode et enveloppées dans une fonction définie en son sein, est appelée une fermeture.
Mark Seemann a quelques exemples intéressants de fermetures dans son article de blog où il fait un parallèle entre la programmation oop et la programmation fonctionnelle.
Et pour le rendre plus détaillé
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.
Les fermetures sont des morceaux de code qui font référence à une variable en dehors d'eux-mêmes, (en dessous d'eux sur la pile), qui pourraient être appelés ou exécutés plus tard, (comme lorsqu'un événement ou un délégué est défini, et pourraient être appelés à un moment futur indéfini ) Parce que la variable externe que le bloc de références de code peut être hors de portée (et aurait sinon été perdue), le fait qu'elle soit référencée par le bloc de code (appelé une fermeture) indique au runtime de "maintenir "cette variable dans la portée jusqu'à ce qu'elle ne soit plus nécessaire par le morceau de fermeture du code ...
J'ai essayé de le comprendre aussi, bien en dessous se trouvent les extraits de code pour le même code en Javascript et C # montrant la fermeture.
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);
}
}
Juste à l'improviste, une réponse simple et plus compréhensive du livre C # 7.0 en bref.
Pré-requis que vous devez savoir : Une expression lambda peut référencer les variables locales et les paramètres de la méthode dans laquelle elle est définie (variables externes).
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
}
Partie réelle : les variables externes référencées par une expression lambda sont appelées variables capturées. Une expression lambda qui capture des variables est appelée fermeture.
Dernier point à noter : les variables capturées sont évaluées lorsque le délégué est effectivement appelé, et non lorsque les variables ont été capturées:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Si vous écrivez une méthode anonyme en ligne (C # 2) ou (de préférence) une expression Lambda (C # 3 +), une méthode réelle est toujours en cours de création. Si ce code utilise une variable locale de portée externe - vous devez toujours transmettre cette variable à la méthode d'une manière ou d'une autre.
par exemple, prenez cette clause Linq Where (qui est une méthode d'extension simple qui transmet une expression 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 vous voulez utiliser i dans cette expression lambda, vous devez le passer à cette méthode créée.
La première question qui se pose est donc: doit-elle être transmise par valeur ou référence?
Le passage par référence est (je suppose) plus préférable car vous obtenez un accès en lecture / écriture à cette variable (et c'est ce que fait C #; je suppose que l'équipe de Microsoft a pesé le pour et le contre et a opté pour une référence par référence; selon Jon Skeet's article , Java est allé avec par valeur).
Mais alors une autre question se pose: où allouer ce i?
Doit-il réellement / naturellement être alloué sur la pile? Eh bien, si vous l'allouez sur la pile et la transmettez par référence, il peut y avoir des situations où elle survit à son propre cadre de pile. Prenez cet exemple:
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'expression lambda (dans la clause Where) crée à nouveau une méthode qui fait référence à un i. Si i est alloué sur la pile d'Outlive, au moment où vous énumérez les whereItems, le i utilisé dans la méthode générée pointera vers le i d'Outlive, c'est-à-dire vers un endroit de la pile qui n'est plus accessible.
Ok, nous en avons donc besoin sur le tas alors.
Donc, ce que le compilateur C # fait pour prendre en charge cette ligne anonyme / lambda, c'est utiliser ce qu'on appelle des " fermetures ": il crée une classe sur le tas appelée ( plutôt mal ) DisplayClass qui a un champ contenant le i, et la fonction qui utilise réellement il.
Quelque chose qui serait équivalent à cela (vous pouvez voir l'IL généré en utilisant ILSpy ou ILDASM):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
Il instancie cette classe dans votre portée locale et remplace tout code relatif à i ou à l'expression lambda par cette instance de fermeture. Donc - chaque fois que vous utilisez le i dans votre code "portée locale" où j'ai été défini, vous utilisez en fait ce champ d'instance DisplayClass.
Donc, si je modifiais le "local" i dans la méthode principale, cela changerait en fait _DisplayClass.i;
c'est à 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
il affichera 12, car "i = 10" va dans ce champ dispalyclass et le modifie juste avant la 2ème énumération.
Une bonne source sur le sujet est ce module Bart De Smet Pluralsight (nécessite une inscription) (ignorez également son utilisation erronée du terme "Levage" - ce qu'il (je pense) veut dire, c'est que la variable locale (c'est-à-dire i) est modifiée pour se référer dans le nouveau champ DisplayClass).
Dans d'autres nouvelles, il semble y avoir une idée fausse selon laquelle les "fermetures" sont liées aux boucles - si je comprends bien, les "fermetures" ne sont PAS un concept lié aux boucles , mais plutôt aux méthodes anonymes / expressions lambda utilisant des variables de portée locales - bien que certaines astuces les questions utilisent des boucles pour le démontrer.
Une fermeture est une fonction, définie au sein d'une fonction, qui peut accéder à ses variables locales ainsi qu'à son parent.
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
donc la fonction à l'intérieur de la méthode find.
t => t.Name == name
peut accéder aux variables dans sa portée, t, et le nom de la variable qui est dans sa portée parent. Même s'il est exécuté par la méthode find en tant que délégué, à partir d'une autre étendue tous ensemble.