1.预热
为了更好的理解委托,先预一下热,看看代码清单1和代码清单2.
代码清单1:
Func<int, int, int> addFunc = (left, right) => left + right; Console.WriteLine(addFunc(12,13));
编译,运行,输出25。这段代码很简单,就是执行加法操作。这段代码看似很简单,但是编译器在背后做了很多的工作。接下来开始深入剖析每一步的内部实现。上面的代码和如下代码是一样的:
代码清单2:
为了便于理解,我故意没有加入using System语句。目的是防止这里定义的Func和C#中的那个泛型Func重名。
2.深入理解委托:
使用ILDASm来查看代码清单1中的main方法的实现,如下:
首先看这几行IL代码:
可以看到,首先入栈了一个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代码如下:
可以看到,编译器使用一个callvirt指令来调用addFunc的Invoke方法,并为这个方法传递了两个参数12,13。这正好是我们在C#中的这条语句实现的:
3.
编译器为addFunc对象传递了一个null空指针,以及一个指向 int32 DelegateThinking.Program::'<Main>b__0'(int32, int32)方法的指针。那么这个奇怪的方法是什么呢?其实只要稍微想一下,也能知道,这个方法是编译器自动创建的,方法的内容就是代码清单1中的加法操作。为了验证这一点,先来看一下int32 DelegateThinking.Program::'<Main>b__0'(int32, int32)方法的实现。如下:
这段指令,首先使用ldarg指令获取了连个参数,然后执行相加,最后返回。可见这就是哪个加法操作的内部实现。也就说,编译器自动为lambda表达式生成了一个方法。
OK,现在一起都清晰了,总结一下,编译器都为我们做了那些工作。
1.编译器会自动为匿名方法或者labda表示自动创建一个对应的方法实现。
2.编译器创建一个委托对象,并为这个对象传递对象的引用,以及一个方法的指针。如果方法是静态的,对象的引用就是null、
3.编译器调用委托对象的Invoke方法,并为这个方法传递对象的参数。
为了更好的理解委托,先预一下热,看看代码清单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方法,并为这个方法传递对象的参数。