C#在列表中添加委托

时间:2016-10-16 13:26:50

标签: c#

以下示例仅打印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

2 个答案:

答案 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开始时实例化一次。
  • DoSomething方法的实现不在IL中。
  • 您会注意到循环是反转的,这取决于编译器。
  • funcPtr表示OpCode ldftn,它获取运行时用作方法指针的int。 C#中没有直接的等价物。
  • .NET中的try-finally块是声明性的,我将它添加到它所属的位置。在.NET中输入try块不是实际的指令。
  • 您还可以在代码中看到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

答案 1 :(得分:1)

那是因为当您将委托添加到列表时,它不会在您将其添加到列表时捕获iCnt变量的值。当你最后调用委托时,它会计算当时的iCnt变量的值,现在是10,从而得到你的结果。

将其视为存储的委托保留指向iCnt变量的指针(不完全正确,但有助于以这种方式对其进行描绘)。只有当您调用委托时,它才会转到指向的位置,获取iCnt的值并使用它。