现在的位置: 首页 > 综合 > 正文

深入剖析委托,匿名委托

2013年09月17日 ⁄ 综合 ⁄ 共 3362字 ⁄ 字号 评论关闭

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方法,并为这个方法传递对象的参数。



















抱歉!评论已关闭.