Est-ce que cet object est un bogue du compilateur C #?

Je répondais à une question sur la possibilité de fermetures (légitimement) prolongeant la durée de vie des objects lorsque j’ai rencontré un code-gen extrêmement curieux de la part du compilateur C # (4.0 si cela est important).

Le plus court reproche que je puisse trouver est le suivant:

  1. Créez un lambda qui capture un local tout en appelant une méthode statique du type conteneur.
  2. Affectez la référence de délégué générée à un champ d’ instance de l’object contenant.

Résultat: le compilateur crée un object de fermeture qui référence l’object qui a créé le lambda, alors qu’il n’a aucune raison: la cible “interne” du délégué est une méthode statique , et les membres de l’instance de l’object lambda-Creating-Object n’ont pas besoin être (et ne pas être) touché lorsque le délégué est exécuté. Effectivement, le compilateur agit comme si le programmeur l’avait capturé sans raison.

 class Foo { private Action _field; public void InstanceMethod() { var capturedVariable = Math.Pow(42, 1); _field = () => StaticMethod(capturedVariable); } private static void StaticMethod(double arg) { } } 

Le code généré à partir d’une version (décompilée en C # plus simple) ressemble à ceci:

 public void InstanceMethod() { c__DisplayClass1 CS$8__locals2 = new c__DisplayClass1(); CS$8__locals2.4__this = this; // What's this doing here? CS$8__locals2.capturedVariable = Math.Pow(42.0, 1.0); this._field = new Action(CS$8__locals2.b__0); } [ComstackrGenerated] private sealed class c__DisplayClass1 { // Fields public Foo 4__this; // Never read, only written to. public double capturedVariable; // Methods public void b__0() { Foo.StaticMethod(this.capturedVariable); } } 

Observez que 4__this champ de l’object de fermeture 4__this avec une référence d’object mais qu’il n’est jamais lu (il n’y a pas de raison).

Alors, que se passe-t-il ici? La spécification de langue le permet-il? Est-ce un bug / oddité du compilateur ou y a-t-il une bonne raison (qui me manque clairement) pour que la fermeture fasse référence à l’object? Cela me rend anxieux, car cela ressemble à une recette pour les programmeurs qui se contentent de fermer (comme moi) d’introduire involontairement des memory leaks étranges (imaginez si le délégué était utilisé comme gestionnaire d’événements) dans les programmes.

Cela ressemble sûrement à un bug. Merci de l’avoir porté à mon attention. Je vais le regarder Il est possible qu’il ait déjà été trouvé et corrigé.

Cela semble être un bug ou inutile:

Je vous lance exemple dans IL lang:

 .method public hidebysig instance void InstanceMethod () cil managed { // Method begins at RVA 0x2074 // Code size 63 (0x3f) .maxstack 4 .locals init ( [0] class ConsoleApplication1.Program/Foo/'<>c__DisplayClass1' 'CS$<>8__locals2' ) IL_0000: newobj instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: ldarg.0 IL_0008: stfld class ConsoleApplication1.Program/Foo ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'<>4__this' //Make ref to this IL_000d: nop IL_000e: ldloc.0 IL_000f: ldc.r8 42 IL_0018: ldc.r8 1 IL_0021: call float64 [mscorlib]System.Math::Pow(float64, float64) IL_0026: stfld float64 ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::capturedVariable IL_002b: ldarg.0 IL_002c: ldloc.0 IL_002d: ldftn instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'b__0'() IL_0033: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0038: stfld class [mscorlib]System.Action ConsoleApplication1.Program/Foo::_field IL_003d: nop IL_003e: ret } // end of method Foo::InstanceMethod 

Exemple 2:

 class Program { static void Main(ssortingng[] args) { } class Foo { private Action _field; public void InstanceMethod() { var capturedVariable = Math.Pow(42, 1); _field = () => Foo2.StaticMethod(capturedVariable); //Foo2 } private static void StaticMethod(double arg) { } } class Foo2 { internal static void StaticMethod(double arg) { } } } 

in cl: (Note !! maintenant la référence a disparu!)

 public hidebysig instance void InstanceMethod () cil managed { // Method begins at RVA 0x2074 // Code size 56 (0x38) .maxstack 4 .locals init ( [0] class ConsoleApplication1.Program/Foo/'<>c__DisplayClass1' 'CS$<>8__locals2' ) IL_0000: newobj instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::.ctor() IL_0005: stloc.0 IL_0006: nop //No this pointer IL_0007: ldloc.0 IL_0008: ldc.r8 42 IL_0011: ldc.r8 1 IL_001a: call float64 [mscorlib]System.Math::Pow(float64, float64) IL_001f: stfld float64 ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::capturedVariable IL_0024: ldarg.0 //No This ref IL_0025: ldloc.0 IL_0026: ldftn instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'b__0'() IL_002c: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0031: stfld class [mscorlib]System.Action ConsoleApplication1.Program/Foo::_field IL_0036: nop IL_0037: ret } 

Exemple 3:

 class Program { static void Main(ssortingng[] args) { } static void Test(double arg) { } class Foo { private Action _field; public void InstanceMethod() { var capturedVariable = Math.Pow(42, 1); _field = () => Test(capturedVariable); } private static void StaticMethod(double arg) { } } } 

en IL: (ce pointeur est de retour)

 IL_0006: ldloc.0 IL_0007: ldarg.0 IL_0008: stfld class ConsoleApplication1.Program/Foo ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'<>4__this' //Back again. 

Et dans les trois cas, la méthode-b__0 () – se ressemblent:

 instance void 'b__0' () cil managed { // Method begins at RVA 0x2066 // Code size 13 (0xd) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld float64 ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::capturedVariable IL_0006: call void ConsoleApplication1.Program/Foo::StaticMethod(float64) //Your example IL_0006: call void ConsoleApplication1.Program/Foo2::StaticMethod(float64)//Example 2 IL_0006: call void ConsoleApplication1.Program::Test(float64) //Example 3 IL_000b: nop IL_000c: ret } 

Et dans les 3 cas, il existe une référence à une méthode statique, ce qui la rend plus étrange. Donc après ce petit parsing, je dirai que c’est un bug / pour rien. !