Recommendation 38: Beware of traps in closures
Let's take a look at the code below and imagine what the output is.
static void Main (string [] args) {List <Action> lists = new list<action> (); for (int i = 0 ; I < 5 ; I++ = () => foreach (Action t in lists) {T (); } }
Our design intent is to have the anonymous method (shown here as a lambda expression) accept parameter I and output:
0
1
2
3
4
and the actual output is:
5
5
5
5
5
This code is not as simple as we thought, to fully understand how run-time code works, first we must understand what the C # compiler does for us.
The IL code is as follows:
. Method Private HidebysigStaticvoidMain (string[] args)CIL managed{ . EntryPoint . Maxstack 3 . Locals Init ( [0]class[mscorlib] System.Collections.Generic.List '1<class[mscorlib]system.action> lists, [1]class[Mscorlib]system.action t, [2]class[Mscorlib]system.action cs$<>9__cachedanonymousmethoddelegate1, [3]classmytest.program/<>c__displayclass2 CS$<>8__LOCALS3, [4]BOOLcs$4$0000, [5] ValueType [Mscorlib]system.collections.generic.list '1/enumerator<class[mscorlib] System.action> cs$5$0001) l_0000: NOP l_0001: newobj instance void[mscorlib] System.Collections.Generic.List '1<class[Mscorlib]system.action>::.ctor ()l_0006: stloc.0 l_0007: ldnull l_0008: Stloc.2 l_0009: newobj instance voidMytest.program/<>c__displayclass2::.ctor ()l_000e: stloc.3 l_000f: Ldloc.3 l_0010: ldc.i4.0 l_0011: stfld Int32mytest.program/<>c__displayclass2::il_0016: BR.Sl_0044l_0018: NOP l_0019: Ldloc.2 l_001a: BRTRUE.Sl_002bl_001c: Ldloc.3 l_001d: ldftn instance voidmytest.program/<>c__displayclass2::<main>b__0 ()l_0023: newobj instance void[mscorlib] System.action::.ctor (Object,nativeint)l_0028: Stloc.2 l_0029: BR.Sl_002bl_002b: Ldloc.2 l_002c: stloc.1 l_002d: ldloc.0 l_002e: Ldloc.1 l_002f: callvirt instance void[mscorlib] System.Collections.Generic.List '1<class[mscorlib] System.action>::ADD(!0) l_0034: NOP l_0035: NOP l_0036: Ldloc.3 l_0037: DUP l_0038: ldfld Int32mytest.program/<>c__displayclass2::il_003d: ldc.i4.1 l_003e: Add l_003f: stfld Int32mytest.program/<>c__displayclass2::il_0044: Ldloc.3 l_0045: ldfld Int32mytest.program/<>c__displayclass2::il_004a: ldc.i4.5 l_004b: CLT l_004d: Stloc.scs$4$0000 l_004f: Ldloc.scs$4$0000 l_0051: BRTRUE.Sl_0018l_0053: NOP l_0054: ldloc.0
The following ellipsis
On the l_0009 line , we found that the compiler created a class "<>c__displayclass2" For us, and each time within the loop, an instance variable I of the class is assigned a value.
The IL code for this class is:
. Class Auto ANSISealednested Private beforefieldinit<>c__displayclass2extends[mscorlib]system.object{. Custom instance void[Mscorlib]system.runtime.compilerservices.compilergeneratedattribute::.ctor (). Method Public Hidebysig specialname rtspecialname instance void. ctor ()CIL managed { } . Method Public Hidebysig instance void<main>b__0 ()CIL managed { } . Field Public Int32i}
After analysis, you will find that the preceding code is actually the same as the following code:
Static voidMain (string[] args) {List<Action> lists =NewList<action>(); Tempclass Tempclass=NewTempclass (); for(tempclass.i =0; Tempclass.i <5; tempclass.i++) {Action T=Tempclass.tempfuc; Lists. ADD (t); } foreach(Action tinchlists) {T (); } } classTempclass { Public inti; Public voidTempfuc () {Console.WriteLine (i.ToString ()); } }
This code demonstrates the closure object. The so-called closure object refers to the Tempclass object in this case (in the first piece of code, the compiler generates the <>c__displayclass2 object for us). If an anonymous method (lambda expression) references a local variable, the compiler automatically promotes the reference to the closure object, modifying the variable i in the For loop to the public variable I that references the closure object. This way, even if the code execution leaves the scope of the original local variable I (such as a For loop), the scope that contains the closure object also exists. Understanding this, you understand the output of the code.
To implement the output expected at the beginning of this recommendation, you can place the generation of the closure object inside the For loop:
Static voidMain (string[] args) {List<Action> lists =NewList<action>(); for(inti =0; I <5; i++) { inttemp =i; Action T= () + ={Console.WriteLine (temp. ToString ()); }; Lists. ADD (t); } foreach(Action tinchlists) {T (); } }
This code is consistent with the following code:
Static voidMain (string[] args) {List<Action> lists =NewList<action>(); for(inti =0; I <5; i++) {Tempclass Tempclass=NewTempclass (); Tempclass.i=i; Action T=Tempclass.tempfuc; Lists. ADD (t); } foreach(Action tinchlists) {T (); } } classTempclass { Public inti; Public voidTempfuc () {Console.WriteLine (i.ToString ()); } }
Turn from: 157 recommendations for writing high-quality code to improve C # programs Minjia
157 recommendations for writing high-quality code to improve C # programs--Recommendation 38: Beware of traps in closures