下面这个例子十次只打印 10。
你能告诉我后台发生了什么吗?
public delegate void DoSomething();
static void Main(string[] args)
{
List<DoSomething> lstOfDelegate = new List<DoSomething>();
int iCnt = 0;
while (iCnt < 10)
{
lstOfDelegate.Add(delegate { Console.WriteLine(iCnt); });
iCnt++;
}
foreach (var item in lstOfDelegate)
{
item.Invoke();
}
Console.ReadLine();
}
根据我的说法,它应该打印 0 到 9,但它打印 10,10,10,10,10,10,10,10,10,10
逻辑
这是代表的一个常见问题,实际上很容易,当你说:
item.Invoke();
委托(delegate)将运行,并且该委托(delegate)是:
Console.WriteLine(iCnt);
iCnt
的值是多少?在那一刻?好吧,它是10
因为你递增它直到它达到那个值。所以它打印10
.
因为您将调用其中的 10 个代表,并且他们都打印 10
, 你得到 10
10 次。
你可以让它使用 iCnt
的值在使用另一个变量创建委托(delegate)的迭代中:
while (iCnt < 10)
{
var value = iCnt;
lstOfDelegate.Add(delegate { Console.WriteLine(value); });
iCnt++;
}
现在,该变量永远不会改变值,委托(delegate)应该按预期工作。
幕后花絮
幕后的实现是使用一个隐藏的匿名类,它有 iCnt
作为一个领域。那么{ Console.WriteLine(iCnt); }
被创建为该类的匿名方法。在运行时,会创建一个指向匿名类实例的委托(delegate),并带有指向匿名方法的指针。
我为等效的完整程序(如下)创建了代码,并使用 Roslyn 编译它以查看它生成的 .IL。
C#代码:
using System;
using System.Collections.Generic;
public class Program
{
public delegate void DoSomething();
public static void Main(string[] args)
{
List<DoSomething> lstOfDelegate = new List<DoSomething>();
int iCnt = 0;
while (iCnt < 10)
{
lstOfDelegate.Add(delegate { Console.WriteLine(iCnt); });
iCnt++;
}
foreach (var item in lstOfDelegate)
{
item.Invoke();
}
Console.ReadLine();
}
}
接下来是对 IL 的重新解释(此代码将不起作用,但显示了编译器编写代码的方式,添加了注释):
public class Program
{
/* This is the delegate class, it uses runtime code, not IL */
public class DoSomething : System.MulticastDelegate
{
public DoSomething(object object, int method) { /*...*/ }
public override void Invoke() { /*...*/ }
public override void BeginInvoke() { /*...*/ }
Public override void EndInvoke() { /*...*/ }
}
/* This is the hidden anonymous class,
the system refers to it as '<>c__DisplayClass1_0'.
notice it contains iCnt. */
[CompilerGenerated()]
private class '<>c__DisplayClass1_0'
{
/* iCnt was moved to here by the compiler. */
public int iCnt;
public '<>c__DisplayClass1_0'() { /* Default constructor */ }
/* This is the method the delegate invokes.*/
internal '<Main>b__0'()
{
Console.WriteLine(this.iCnt);
}
}
public static void Main(string[] args)
{
'<>c__DisplayClass1_0' var0; // A reference to the anonymous class
List<DoSomething> var1; // lstOfDelegate
int var2; // temp variable for the increment
bool var3; // temp variable for the while conditional
List<DoSomething>.Enumerator var4; // enumerator, used by foreach
DoSomething var5; // temp variable for the delegate
// Instantiate the anonymous class
// As you can see, there is only one instance,
// so there is only one iCnt
var0 = new '<>c__DisplayClass1_0'();
// List<DoSomething> lstOfDelegate = new List<DoSomething>();
var1 = new List<DoSomething>();
// int iCnt = 0;
var0.iCnt = 0;
goto IL_003b; // while (iCnt < 10) {
IL_0016:
// lstOfDelegate.Add(delegate { Console.WriteLine(iCnt); });
var1.add(new DoSomething(var0, funcPtr('<>c__DisplayClass1_0'.'<Main>b__0')));
// iCnt++;
var2 = var0.iCnt;
var0.iCnt = var2 + 1;
IL_003b:
var3 = var0.iCnt < 10;
if (var3) goto IL_0016; // }
var4 = var1.GetEnumerator();
goto IL_0067; // foreach (var item in lstOfDelegate) {
try {
IL_0054:
var5 = var4.Current;
var5.Invoke();
IL_0067:
if (var4.MoveNext()) goto IL_0054;
} finally {
var4.Dispose();
}
Console.ReadLine();
}
public Program() { /* Default constructor */ }
}
注意事项:
- 如您所见,添加到列表中的所有代表实际上都是相同的。
- 在循环中添加新变量时,它会将语义更改为每次迭代都有一个新变量。然后编译器生成代码来实例化一个新的
'<>c__DisplayClass1_0'
在每次迭代中,而不是在 Main
开始时一次.
- DoSomething 方法的实现不在 IL 中。
- 您会注意到循环是反向的,这取决于编译器。
-
funcPtr
代表 OpCode ldftn
,它得到一个 int ,运行时将其用作指向方法的指针。 C# 中没有直接等效项。
- .NET中的try-finally block 是声明式的,我把它加在了它所属的位置上。在 .NET 中,进入 try block 并不是实际指令。
- 您还可以在代码中看到 foreach 循环的结构。
- 生成的代码始终使用全名限定,为了便于阅读,我将其删除。
希望阅读上面的代码可以消除任何疑问。为了以防万一,我在下面添加了 Roslyn 生成的 IL:
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
// Nested Types
.class nested public auto ansi sealed DoSomething
extends [mscorlib]System.MulticastDelegate
{
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
object 'object',
native int 'method'
) runtime managed
{
} // end of method DoSomething::.ctor
.method public hidebysig newslot virtual
instance void Invoke () runtime managed
{
} // end of method DoSomething::Invoke
.method public hidebysig newslot virtual
instance class [mscorlib]System.IAsyncResult BeginInvoke (
class [mscorlib]System.AsyncCallback callback,
object 'object'
) runtime managed
{
} // end of method DoSomething::BeginInvoke
.method public hidebysig newslot virtual
instance void EndInvoke (
class [mscorlib]System.IAsyncResult result
) runtime managed
{
} // end of method DoSomething::EndInvoke
} // end of class DoSomething
.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass1_0'
extends [mscorlib]System.Object
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Fields
.field public int32 iCnt
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x20f4
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method '<>c__DisplayClass1_0'::.ctor
.method assembly hidebysig
instance void '<Main>b__0' () cil managed
{
// Method begins at RVA 0x20fd
// Code size 14 (0xe)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld int32 Program/'<>c__DisplayClass1_0'::iCnt
IL_0007: call void [mscorlib]System.Console::WriteLine(int32)
IL_000c: nop
IL_000d: ret
} // end of method '<>c__DisplayClass1_0'::'<Main>b__0'
} // end of class <>c__DisplayClass1_0
// Methods
.method public hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 136 (0x88)
.maxstack 3
.locals init (
[0] class Program/'<>c__DisplayClass1_0',
[1] class [mscorlib]System.Collections.Generic.List`1<class Program/DoSomething>,
[2] int32,
[3] bool,
[4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class Program/DoSomething>,
[5] class Program/DoSomething
)
IL_0000: newobj instance void Program/'<>c__DisplayClass1_0'::.ctor()
IL_0005: stloc.0
IL_0006: nop
IL_0007: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class Program/DoSomething>::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.0
IL_000e: ldc.i4.0
IL_000f: stfld int32 Program/'<>c__DisplayClass1_0'::iCnt
IL_0014: br.s IL_003b
IL_0016: nop
IL_0017: ldloc.1
IL_0018: ldloc.0
IL_0019: ldftn instance void Program/'<>c__DisplayClass1_0'::'<Main>b__0'()
IL_001f: newobj instance void Program/DoSomething::.ctor(object, native int)
IL_0024: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class Program/DoSomething>::Add(!0)
IL_0029: nop
IL_002a: ldloc.0
IL_002b: ldfld int32 Program/'<>c__DisplayClass1_0'::iCnt
IL_0030: stloc.2
IL_0031: ldloc.0
IL_0032: ldloc.2
IL_0033: ldc.i4.1
IL_0034: add
IL_0035: stfld int32 Program/'<>c__DisplayClass1_0'::iCnt
IL_003a: nop
IL_003b: ldloc.0
IL_003c: ldfld int32 Program/'<>c__DisplayClass1_0'::iCnt
IL_0041: ldc.i4.s 10
IL_0043: clt
IL_0045: stloc.3
IL_0046: ldloc.3
IL_0047: brtrue.s IL_0016
IL_0049: nop
IL_004a: ldloc.1
IL_004b: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class Program/DoSomething>::GetEnumerator()
IL_0050: stloc.s 4
IL_0052: br.s IL_0067
IL_0054: ldloca.s 4
IL_0056: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class Program/DoSomething>::get_Current()
IL_005b: stloc.s 5
IL_005d: nop
IL_005e: ldloc.s 5
IL_0060: callvirt instance void Program/DoSomething::Invoke()
IL_0065: nop
IL_0066: nop
IL_0067: ldloca.s 4
IL_0069: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class Program/DoSomething>::MoveNext()
IL_006e: brtrue.s IL_0054
IL_0070: leave.s IL_0081
IL_0072: ldloca.s 4
IL_0074: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class Program/DoSomething>
IL_007a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_007f: nop
IL_0080: endfinally
IL_0081: call string [mscorlib]System.Console::ReadLine()
IL_0086: pop
IL_0087: ret
Try IL_0052-IL_0072 Finally IL_0072-IL_0081
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x20f4
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
} // end of class Program