zoukankan      html  css  js  c++  java
  • 进一步理解委托

    前面一篇文章介绍了委托的基本知识,接下来就进一步研究一下委托。

    委托类型

    其实,刚开始觉得委托类型是一个比较难理解的概念,怎么也不觉得下面的"AssembleIphoneHandler"是一个类型。

    public delegate void AssembleIphoneHandler();

     按照正常的情况,如果我们要创建一个委托类型应该是:

    public class AssembleIphoneHandler : System.MulticastDelegate
    {
    }

     但是,这种写法是编译不过的,会提示不能从"System.MulticastDelegate"派生子类。

    其实,这里是编译器为我们做了一个转换,当我们使用delegate关键字声明一个委托类型的时候,编译器就会按照上面代码片段中的方式为我们创建一个委托类型。

    知道了这些东西,对于委托类型的理解就比较容易了,通过delegate声明的委托类型就是一个从"System.MulticastDelegate"派生出来的子类。

    建立委托链

    下面我们通过一个例子来看看委托链的建立,以及调用列表的变化,基于前面一篇文章中的例子进行一些修改。

    class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Apple();
            Foxconn foxconn = new Foxconn();
    
            Apple.AssembleIphoneHandler d1, d2, d3, d4 = null;
            d1 = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
            d2 = new Apple.AssembleIphoneHandler(foxconn.PackIphone);
            d3 = new Apple.AssembleIphoneHandler(foxconn.ShipIphone);
    
            d4 += d1;
            d4 += d2;
            d4 += d3;
    
            d4();
    
            Console.Read();
        }
    }

     我们接下来进行一下单步调试看看委托链建立的过程。

    1. 当下面三句执行完成后,可以通过VS看到d1、d2和d3的详细信息

    d1 = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);

    d2 = new Apple.AssembleIphoneHandler(foxconn.PackIphone);

    d3 = new Apple.AssembleIphoneHandler(foxconn.ShipIphone);

    对于上面三个委托实例来说:

    • 调用列表为空,所以_invocationCount为0,_invocationList为空
    • _target代表创建委托实例的方法来自Foxconn的实例;如果是静态方法创建的委托实例_target值为null
    • _methodPtr代表这个方法的唯一标识,可以理解为句柄
    • _methodBase包含创建委托实例的方法的信息,方法名、返回类型等等

    2. 通过"+="操作符来进行委托合并

    d4 += d1;

    这时,由于d4初始值为null,在使用"+="操作(Combine方法)构造委托链时,将返回另外一个参数d1,再将d1的引用赋给d4(通过"ILSpy"查看,如下图)。也就是说,这时d4将指向d1所指向的对象。

    3. 继续执行委托合并,并查看d4的变化

    d4 += d2;

    这时可以看到调用列表的变化,_invocationList包含两个元素,分别是d1和d2.

    4. 最后进行一次委托合并,把d3合并到d4中

    d4 += d3;

    可以看到最新的d4实例中,调用列表已经包含了d3。

    注意:由于委托是不可变的,所以这里应该描述为,d3和d4的Combine 产生了一个新的委托实例,新的委托实例的调用列表是d3和d4的合并;操作完成后,d4变量将指向新的委托实例的引用。

    疑问:其实在这步调试过程中有个疑问,_invocationCount的值是3,但是_invocationList中有四个元素,最后一个为null,找了一下也没发现为什么,望高手看到帮忙解答。

    所以对委托链建立的方法Delegate.Combine(Delegate A, Delegate B),可以进行下面的概括:

    • 如果A和B均为null,则返回null。
    • 如果A或B一个为null而另一个不为null,则返回不为null的委托。
    • 如果A和B均不为null,返回一个新的委托(委托是不可变的),该委托_invocationList字段为一个委托数组,该数组中委托的顺序为:A中_invacationList所指向的委托数组 + B中_invacationList所指向的委托数组。

    移除委托链

    我们可以通过Delegate类的静态方法Remove,从一个委托链中移除一个委托,这里就不做演示了。

    注意:当调用Remove时,会遍历(倒序)第一个参数中的中的调用列表(_invocationList), 找到与第二个参数的_target和_methodPtr字段相匹配的委托,并将其从委托列表中移除。

    当有多个匹配的情况是,Remove方法只移除第一个匹配的委托;但是,可以通过RemoeAll方法来移除所有匹配的委托。

    同样对委托移除的方法Delegate.Remove(Delegate A, Delegate B),可以进行下面的概括:

    • 如果A为null,返回null。
    • 如果B为null,返回A。
    • 如果A的_invocationList为null,即不包含委托链,那么如果A本身与B匹配,则返回null,否则返回A。
    • 如果A的_invocationList中不包含与B匹配的委托,则返回A。
    • 如果A的_invocationList中包含与B匹配的委托,则从链表中移除B,然后
      • 如果A的链表中只剩下一个委托,则返回该委托。
      • 如果A的链表中还剩下多个委托,将重新构建一个新的委托,并且新的委托的_invocationList为A的_invocationList移除了B之后的List。

    总结

    通过这篇文章,进一步认识了委托类型,然后通过一个例子观察了委托链的建立以及调用列表的变化。

    通过这两篇文章,对委托应该有了一定的认识:

    • 通过delegate关键字声明委托类型
      • [<修饰符>] delegate <返回类型> <委托名> ([<形参表>])
    • 找到与委托签名相符的方法来创建委托实例,也可以通过"+="和"-="来组合和移除委托
      • new <委托类型名> (<方法>)
    • 通过委托实例调用委托

     

  • 相关阅读:
    Pascal's Triangle II
    Pascal's Triangle
    Best Time to Buy and Sell Stock II
    Best Time to Buy and Sell Stock
    Populating Next Right Pointers in Each Node
    path sum II
    Path Sum
    [转载]小波时频图
    [转载]小波时频图
    [转载]Hilbert变换及谱分析
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4282407.html
Copyright © 2011-2022 走看看