以下示例仅打印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
答案 0 :(得分:2)
这是代表们的常见问题,实际上很容易,当你说:
item.Invoke();
委托将运行,该委托是:
Console.WriteLine(iCnt);
那时iCnt
的价值是什么?它是10
,因为你增加它直到达到该值。所以它会打印10
。
由于你会打电话给其中10位代表,而且他们都打印10
,你会得到10
10次。
您可以在使用另一个变量创建委托的迭代中使用iCnt
的值:
while (iCnt < 10)
{
var value = iCnt;
lstOfDelegate.Add(delegate { Console.WriteLine(value); });
iCnt++;
}
现在,该变量永远不会改变值,并且委托应该按预期工作。
幕后实现使用隐藏的匿名类,其中iCnt
为字段。然后创建{ Console.WriteLine(iCnt); }
作为该类的匿名方法。在运行时,创建一个委托,指向匿名类的实例,并带有指向匿名方法的指针。
我为等效的完整程序(下面)创建了代码,并使用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
开始时实例化一次。funcPtr
表示OpCode ldftn
,它获取运行时用作方法指针的int。 C#中没有直接的等价物。希望阅读上面的代码可以解决任何疑问。为了以防万一,我在下面添加了由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
答案 1 :(得分:1)
那是因为当您将委托添加到列表时,它不会在您将其添加到列表时捕获iCnt变量的值。当你最后调用委托时,它会计算当时的iCnt变量的值,现在是10,从而得到你的结果。
将其视为存储的委托保留指向iCnt变量的指针(不完全正确,但有助于以这种方式对其进行描绘)。只有当您调用委托时,它才会转到指向的位置,获取iCnt的值并使用它。