委托那些事儿

一、定义

委托类似于C++的函数指针,但是委托时类型安全的。一个不好听的比喻,生前写了一个遗嘱,死后遗嘱才会公开。委托的意义就是在某个特定的时间做某事,比如点击一个按钮会发生某事,但是你不想修改按钮的代码,可以添加一个方法到委托上,当点击按钮时,会自动执行委托链上的方法。委托增加了复杂性但是也增加了灵活性,方便程序的设计。

二、委托与事件

让委托工作起来需要的步骤:

  1. 声明委托类型。

  2. 创建委托实例。

  3. 为委托实例添加匹配的方法。

  4. 调用委托实例。

下面是一个简单的委托例子。

版本一:
class Patent1{    // 声明委托类型    public delegate void PatentHandler();    // 创建委托实例    public PatentHandler patentHandler = null;    // 时间限制,到100    private int limit;    public Patent1(int limit)    {        this.limit = limit;    }    // 驱动函数    public void Grow(int limit)    {        this.limit += limit;        // 时间到100时,调用委托实例        if (this.limit >= 100 && this.patentHandler !=null)        {            patentHandler();        }    }}

主函数调用:

static void Main(){    Patent1 patent = new Patent1(40);        // 为委托实例添加方法,Expire方法是与委托类型匹配的方法    patent.patentHandler = new Patent1.PatentHandler(Expire);       // limit没到100,不调用委托实例    patent.Grow(30);       // 调用委托实例    patent.Grow(30);}// 与PatentHandler委托匹配的方法static void Expire(){    Console.WriteLine("limit到了100,函数被触发...");}

从两面的例子中可以看到4个步骤。上例子中委托实例的访问修饰符是public,所以在其他类可以自由调用,于是问题出现了:

假如主函数调用改为

static void Main(){    Patent1 patent = new Patent1(40);

patent.patentHandler =null;

// 为委托实例添加方法,Expire方法是与委托类型匹配的方法    patent.patentHandler = new Patent1.PatentHandler(Expire);    // 直接调用委托实例

patent.patentHandler.Invoke();

// limit没到100,不调用委托实例    patent.Grow(30);       // 调用委托实例    patent.Grow(30);}

问题一:

添加了一个语句patent.patentHandler.Invoke();,这样直接就可以调用委托实例了,例子中的情景:专利的年限是100,当满100的时候,才能公开,即调用委托实例。但是客户端调用的时候却可以绕过limit的限制,直接调用委托实例,这就导致了专利年限没满就公开了,大大的不好!

问题二:

添加了一个语句patent.patentHandler =null;将委托实例的调用列表清空。

版本二:

为了避免上述的两个问题,需要将委托实例的声明为private。但是需要给委托实例注册函数,但是委托实例是私有的,因此需要通过两个函数来帮助委托实例注册和取消注册函数。好比一个私有字段,需要两个函数getter和setter函数来辅助。于是有一个新版本,比第一个版本多了一下代码

// 创建委托实例,声明为private       private PatentHandler patentHandler;       // 为委托实例注册函数       public void Add_Delegate(PatentHandler ph)       {           if (this.patentHandler == null)           {               this.patentHandler = ph;           }           else           {               this.patentHandler += ph;           }       }       // 为委托实例取消注册函数       public void Remove_Delegate(PatentHandler ph)       {           if (this.patentHandler != null)           {               this.patentHandler -= ph;           }       }

主函数调用:

Patent2 patent = new Patent2(40);patent.Add_Delegate(new Patent2.PatentHandler(Expire));patent.Add_Delegate(new Patent2.PatentHandler(Expire1));patent.Remove_Delegate(new Patent2.PatentHandler(Expire));//patent.patentHandler.Invoke();patent.Grow(60);

由于委托实例为私有,所以不存在版本一的两种问题。为了解决这个问题每次都得自己写两个辅助函数,是在是太麻烦了。要是有个东西可以把两个函数封装起来就好了。于是语法糖 事件(event)出现了。

版本三:
class Patent3{    // 声明委托类型    public delegate void PatentHandler();

publiceventPatentHandler patentEvent;

private int limit;    public Patent3(int limit)    {        this.limit = limit;    }    // 驱动函数    public void Grow(int limit)    {        this.limit += limit;        if (this.limit >= 100 && this.patentEvent != null)        {

patentEvent();

}    }}

与第一个版本的区别用红色标出。

主函数调用:

Patent3 patent = new Patent3(40);patent.patentEvent += new Patent3.PatentHandler(Expire);patent.Grow(60);

功能和版本二一样,但是却简洁了很多。那事件到底是何方神圣。

QQ截图20120614235620

声明一个事件之后,编译器会将事件转换为具有默认实现的add_xxx/remove_xxx的方法和一个私有委托实例字段。私有委托实例字段对类内可见,成对的add/remove方法对类外可见,对应于代码中的+=和-=。

因此:事件不是委托实例,而是成对的对私有委托实例字段进行操作的已实现的add/remove方法,类似于属性的getter和setter方法,为的是封装数据。

三、委托的历史

c#1.0

QQ截图20120615001405

c#2.0

QQ截图20120615001422

在c#2.0中添加了 方法组的隐式转换。

QQ截图20120615001500

添加了匿名函数和委托的协变性/逆变性。

c#3.0

QQ截图20120615002159

lambda表达式。
原文链接: https://www.cnblogs.com/alab/archive/2012/06/15/2550099.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/52800

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 上午4:13
下一篇 2023年2月9日 上午4:13

相关推荐