1.预热
为了更好的理解委托,先预一下热,看看代码清单1和代码清单2.
代码清单1:
Func<int, int, int> addFunc = (left, right) => left + right; Console.WriteLine(addFunc(12,13));
编译,运行,输出25。这段代码很简单,就是执行加法操作。这段代码看似很简单,但是编译器在背后做了很多的工作。接下来开始深入剖析每一步的内部实现。上面的代码和如下代码是一样的:
代码清单2:
namespace DelegateThinking { delegate int Func(int left, int right); class Program { static void Main(string[] args) { Func addFunc = Add; addFunc(12, 13); } public static int Add(int left, int right) { return left + right; } }
为了便于理解,我故意没有加入using System语句。目的是防止这里定义的Func和C#中的那个泛型Func重名。
2.深入理解委托:
使用ILDASm来查看代码清单1中的main方法的实现,如下:
method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 50 (0x32) .maxstack 3 .locals init ([0] class [mscorlib]System.Func`3<int32,int32,int32> addFunc) IL_0000: nop IL_0001: ldsfld class [mscorlib]System.Func`3<int32,int32,int32> DelegateThinking.Program::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0006: brtrue.s IL_001b IL_0008: ldnull IL_0009: ldftn int32 DelegateThinking.Program::'<Main>b__0'(int32, int32) IL_000f: newobj instance void class [mscorlib]System.Func`3<int32,int32,int32>::.ctor(object, native int) IL_0014: stsfld class [mscorlib]System.Func`3<int32,int32,int32> DelegateThinking.Program::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0019: br.s IL_001b IL_001b: ldsfld class [mscorlib]System.Func`3<int32,int32,int32> DelegateThinking.Program::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0020: stloc.0 IL_0021: ldloc.0 IL_0022: ldc.i4.s 12 IL_0024: ldc.i4.s 13 IL_0026: callvirt instance !2 class [mscorlib]System.Func`3<int32,int32,int32>::Invoke(!0, !1) IL_002b: call void [mscorlib]System.Console::WriteLine(int32) IL_0030: nop IL_0031: ret }
首先看这几行IL代码:
IL_0008: ldnull IL_0009: ldftn int32 DelegateThinking.Program::'<Main>b__0'(int32, int32) IL_000f: newobj instance void class [mscorlib]System.Func`3<int32,int32,int32>::.ctor(object, native int) IL_0014: stsfld class [mscorlib]System.Func`3<int32,int32,int32> DelegateThinking.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
可以看到,首先入栈了一个null空指针,紧接着入栈了一个指向 int32 DelegateThinking.Program::'<Main>b__0'(int32, int32)方法的指针,然后将他们做为参数传递给Func的构造函数,来初始化Func对象。用C#来描述上述IL指令,如下:
System.Func`3<int32,int32,int32> addFunc=new System.Func`3<int32,int32,int32>(null,
DelegateThinking.Program::'<Main>b__0'(int32,int32));
很好,创建好了addFunc委托对象之后,就可以调用这个委托了,对应的IL代码如下:
IL_0022: ldc.i4.s 12 IL_0024: ldc.i4.s 13 IL_0026: callvirt instance !2 class [mscorlib]System.Func`3<int32,int32,int32>::Invoke(!0,
可以看到,编译器使用一个callvirt指令来调用addFunc的Invoke方法,并为这个方法传递了两个参数12,13。这正好是我们在C#中的这条语句实现的:
addFunc(12,13)
3.
编译器为addFunc对象传递了一个null空指针,以及一个指向 int32 DelegateThinking.Program::'<Main>b__0'(int32, int32)方法的指针。那么这个奇怪的方法是什么呢?其实只要稍微想一下,也能知道,这个方法是编译器自动创建的,方法的内容就是代码清单1中的加法操作。为了验证这一点,先来看一下int32 DelegateThinking.Program::'<Main>b__0'(int32, int32)方法的实现。如下:
.method private hidebysig static int32 '<Main>b__0'(int32 left, int32 right) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // 代码大小 8 (0x8) .maxstack 2 .locals init ([0] int32 CS$1$0000) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: add IL_0003: stloc.0 IL_0004: br.s IL_0006 IL_0006: ldloc.0 IL_0007: ret }
这段指令,首先使用ldarg指令获取了连个参数,然后执行相加,最后返回。可见这就是哪个加法操作的内部实现。也就说,编译器自动为lambda表达式生成了一个方法。
OK,现在一起都清晰了,总结一下,编译器都为我们做了那些工作。
1.编译器会自动为匿名方法或者labda表示自动创建一个对应的方法实现。
2.编译器创建一个委托对象,并为这个对象传递对象的引用,以及一个方法的指针。如果方法是静态的,对象的引用就是null、
3.编译器调用委托对象的Invoke方法,并为这个方法传递对象的参数。