C#委托内部使用局部的变量的问题

一. 引子

先来看如下代码:

int i = 0;
Action action1 = () =>
{
Console.WriteLine("打印一下i的值:" + i);
};
i = 1;
Action action2 = () =>
{
Console.WriteLine("打印一下i的值:" + i);
};
action1.Invoke();
action2.Invoke();

那么问题来了,执行这两个委托之后,输出的是

打印一下i的值:0

打印一下i的值:1

还是

打印一下i的值:1

打印一下i的值:1

相信有一些人会觉的是第一种情况,但是实际的结果却是第二种情况的结果,如下图测试结果,这是为什么呢?

 

 

 二. 原理部分

首先我们会议一下C#中委托究竟是什么?

相信很多的小伙伴都知道,委托是一种语法糖。它在编辑之后,编译器会为其生成一个类。我们使用 ILSpy.exe 来看一下编译之后生成的是什么:

ILSpy.exe 打开,如下图:

 

 

 

 这个自动生成的类 "<>c__DisplayClass0_0" 便是委托生成的类,看一下IL代码:

.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0'
    extends [mscorlib]System.Object
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Fields
    .field public int32 i

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20a0
        // 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__DisplayClass0_0'::.ctor

    .method assembly hidebysig 
        instance void '<Main>b__0' () cil managed 
    {
        // Method begins at RVA 0x20a9
        // Code size 29 (0x1d)
        .maxstack 8

        // (no C# code)
        IL_0000: nop
        // Console.WriteLine("打印一下i的值:" + this.i);
        IL_0001: ldstr "打印一下i的值:"
        IL_0006: ldarg.0
        IL_0007: ldfld int32 Demo.Program/'<>c__DisplayClass0_0'::i
        IL_000c: box [mscorlib]System.Int32
        IL_0011: call string [mscorlib]System.String::Concat(object, object)
        IL_0016: call void [mscorlib]System.Console::WriteLine(string)
        // (no C# code)
        IL_001b: nop
        IL_001c: ret
    } // end of method '<>c__DisplayClass0_0'::'<Main>b__0'

    .method assembly hidebysig 
        instance void '<Main>b__1' () cil managed 
    {
        // Method begins at RVA 0x20a9
        // Code size 29 (0x1d)
        .maxstack 8

        // (no C# code)
        IL_0000: nop
        // Console.WriteLine("打印一下i的值:" + this.i);
        IL_0001: ldstr "打印一下i的值:"
        IL_0006: ldarg.0
        IL_0007: ldfld int32 Demo.Program/'<>c__DisplayClass0_0'::i
        IL_000c: box [mscorlib]System.Int32
        IL_0011: call string [mscorlib]System.String::Concat(object, object)
        IL_0016: call void [mscorlib]System.Console::WriteLine(string)
        // (no C# code)
        IL_001b: nop
        IL_001c: ret
    } // end of method '<>c__DisplayClass0_0'::'<Main>b__1'

} // end of class <>c__DisplayClass0_0

我们可以看到,这个自动生成的类中,它有两个方法 "<Main>b__0" 和 "<Main>b__1" ,分别对应的是委托 action1 和 action2,于此同时还有一个 public 的成员字段 i,用来保存外部传递的 i  的值。很明显,由于第二次的赋值,把 i 赋值成了1,所以会导致两个方法的 i 的值因为共用一个成员变量而都成为1。

三. 解决方案

那么问题来了,我们怎么样才能用局部的临时变量同时赋值给两个委托而对它们不会造成互相影响呢?

1. 设置连个不同的临时变量(感觉有点笨)

程序代码:

int i = 0;
Action action1 = () =>
{
Console.WriteLine("打印一下i的值:" + i);
};
int j = 1;
Action action2 = () =>
{
Console.WriteLine("打印一下i的值:" + j);
};
action1.Invoke();
action2.Invoke();

2. 将临时变量的值按照委托的参数传入

程序代码:

Action<int> action1 = (num) =>
{
Console.WriteLine("打印一下i的值:" + num);
};
action1.Invoke(0);
action1.Invoke(1);
posted @ 2019-11-22 09:49  霁雪湖上三映月  阅读(910)  评论(0编辑  收藏  举报