委托是用来处理其他语言(如 C++、Pascal 和 Modula)需用函数指针来处理的情况的。不过与 C++ 函数指针不同,委托是完全面对对象的;另外,C++ 指针仅指向成员函数,而委托同时封装了对象实例和方法。

委托实例的一个有趣且有用的属性是:它不知道也不关心它所封装的方法所属的类它所关心的仅限于这些方法必须与委托的类型兼容。这使委托非常适合于“匿名”调用。

  • 第一步:声明一个委托

    委托神似普通方法,在返回类型前面加一个delegate关键字,并且没有方法体。
  • 打开ILSpy看一下

    NoReturnNoPara继承自System.MulticastDelegate。我们看一下MulticastDelegate这是个什么东东

    MulticastDelegate是一个abstract修饰的类,表示多路广播委托(多播委托)继承自Delegate。所以委托是一个类。

    既然MulticastDelegate是一个类,那我们可否自己写一个类继承自MulticastDelegate,说干就干,代码如下。

    MulticastDelegate是一个特殊类,.Net框架内置类型,无法手动派生。

  • 第二步: 实例化委托

    实例化委托是将某一个对象实例的方法引用传入,method里面不仅保存了DoNothing方法,也包含了拥有该方法的对象实例。

  • 第三步: 委托调用

    执行method.Invoke()这句话时,程序会进入method保存的对象实例方法中(DoNothing)。

    委托异步调用会重新开启一个线程去执行,异步多线程后面会讲到。没有委托就没有异步多线程,所以委托的地位也是举足轻重的,以上三步简称委托三部曲。

    看到这你可能会有疑问,我直接调用DoNothing不就完了,还搞什么一步二步三步,折腾了半圈最后还是执行DoNothing方法。没错,最终都是在执行DoNothing方法,但是人生就是要折腾,自己的活就是要委托给别人干。废话了一段,下面我举两个例子展示下委托的作用(普通方法办不到的事情)。

  • 1. 回调
    举一个Winfrom窗体传值的例子,例子虽小但是精髓都在呦!
    场景1说明:父窗体有一button按钮,子窗体有一label控件,每点击一次父窗体的button,子窗体的label数值相应的加一。父窗体代码如下:

    子窗体代码如下:

    场景分析: 父窗体点击按钮后,子窗体跟着变化,所以说父窗体是消息发送者,所以委托Action应该在父窗体内部声明,因为它需要在button1_Click方法里面去Invoke()发送消息。 子窗体是消息的响应者,如何响应,创建一个和父窗体委托签名一致的方法进行响应或者接收父窗体传入的值。 二者通过action = childFrm.ShowLabel;这句代码进行关联,父窗体一旦有风吹草动,子窗体的ShowLabel方法就会被执行。

  • 场景2说明:子窗体有一button按钮,父窗体有一label控件,每点击一次子窗体的button,就将一个num数值传到父窗体Label控件中进行展示。子窗体代码如下:

    父窗体代码如下:

    场景分析:子窗体点击按钮,传值给父窗体,子窗体是消息的发送者,所以委托Action<int>应该在子窗体内部声明,因为它需要在btnChildShow_Click方法里面去Invoke()发送消息,由于在子窗体里面没有父窗体的对象,无法像这样action=父对象.ShowMainLabel;实例化(此处并不是说父窗体对象获取不到,其实是能获取到的,只是用委托方式无需获取父窗体对象)。此路不通,换另一条路,子窗体中无父窗体对象,但是父窗体中有子窗体对象,所以只需稍稍一变通,childFrm.action = ShowMainLabel; 流程瞬间变得明朗。

    如果你理解了,我相信你会感慨委托之强大。不知现在的你还纠结一步二步三步吗?(一步两步,一步两步,一步一步似爪牙,纳尼?跑远了)。如果你还纠结,还是不太理解委托这玩意有啥用,好吧,好人做到底,再来看委托的另一个作用。

  • 2. 解耦
    我们写一个用汉语打招呼的方法。

    对接上中国提供的接口,没问题,输出“你好,委托”,恰巧你身边有一个英国朋友,名叫”Delegate”,不懂汉语,你需要用英语和他打招呼。

    你只有一个SayHi方法,要同时对接两种语言提供的接口。怎么办呢?简单,传入一个枚举类型,再作一下判断就搞定了。如下:

    但是你交友广泛,世界各地的朋友你都熟(原始森林里食人族的酋长你都认识),你需要通过SayHi方法向他们表示问好。So easy! 哥既然有本事结交他们,当然也有能力向他们问好,当你在SayHi方法里对接到第5000个if(食人族官方语言接口)时,开始怀疑人生了。你在想,我新结交一个朋友,那个唯一的SayHi接口就要改动一次,太TM累了,上天能不能赐我一个万能的,一劳永逸的接口。我说朋友,首先,你的接口设计违反了开闭原则,第二,你没有最先认识我(先认识了那个叫委托的人),不然你早就能自由自在的打招呼了。第三,你没有深入了解一下你那个哥们。(PS:你就是先认识我,我也让你去找一个名叫委托的人深入了解一下,手动滑稽)

    言归正传,我们分析一下那个含有枚举类型的SayHi方法:

    1. 参数传入了枚举类型,根据枚举类型去实现逻辑
    2. if部分都是变化的逻辑,是否可以封装出去
    3. 我们能不能不传枚举类型,一步到位,直接传进来一个逻辑,这样只需要执行一下逻辑就行了。

    当然可以,此时我们的委托该登场了,上面说的逻辑其实就是一个方法。

    委托可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,降低耦合度,同时使得程序具有更好的可扩展性。

    如果不好理解你就记住,参数传进来的是一个委托(如:action),你打开VS,自己建一个方法,参数就传入委托,从带参数到不带参数,从无返回值到有返回值(Func),边写边思考。

    既然委托可以传递一个方法,那能不能传递多个方法呢?调用一次SayHi方法,一次性向各界朋友打招呼,当然是可以的,因为委托可以绑定多个方法,然后再依次调用(多播)。代码如下:

    输出结果: 你好,Everybody  和 Hi, Everybody

    有“+=” 当然也有“-=”,从委托身上解除绑定某个方法。

    输出结果: Hi, Everybody。

    SayHi方法也可以用Lambda表达式进行简化

    委托的绑定也可以用Lambda表达式进行绑定,如下所示

    现在我们进行移除-=

    结果是移除不掉,两个方法都执行了一遍。因为Lambda表达式内部重新声明了一个委托,绑定的和移除的是两个不同的东西。
    下面再写两个复杂的,思考一下。

    第一个是在Lambda表达式和Linq一文中封装的WydWhere方法

    第二个是ADO.Net操作的封装

  • 事件
    创建一个类,里面声明一个委托、委托方法和事件、事件方法。

    事件: 是一个委托的实例,加上一个event关键字。

    如上代码所示: action是一个委托的实例,actionEvent也是委托的实例,只是前面加上了一个event关键字变成了事件。

    那加上event关键字什么用呢? event关键字限制了权限,保证安全。在内部类看不出来,我们换到外部调用看一下。

    So,事件的诸多操作在外部是无法实现的,只允许绑定动作,保证了安全。

    现在用Winform的Button控件的Click事件说明一下,事件有什么用途。

    首先Click事件是谁提供的?微软的类库Control内部提供的。你想想微软会让你去肆意动他的类库吗?你只能在外部进行动作绑定,不允许你对Click事件有过多的操作,保证安全。再者,你点击一下button按钮,你的迎娶白富美梦想就实现了,但是微软知道吗?微软知道你是升职加薪,还是停职降薪,知道你是走上人生巅峰,还是走上人生低谷,微软不知道。所以这部分微软是不可能在内部帮你实现的,微软只能提供给你一个按钮,让你去触发它,同时再给你一个btnShow_Click方法让你在里面写下你的梦想,自动帮你绑定到控件事件中,然后微软在Control类内部判定你是否触发了按钮,在内部帮你把你写的梦想Invoke一下。这个过程,就像你脸皮薄,不想直接告诉别人我要升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰。但是你选择了把你的梦想委托给微软帮你去发布,去Invoke。

如果还是不懂,没关系,多看多写多思考,突然有一天你就理解了,就是那么神奇。

如果转载,请给出原文链接,谢谢!


微信支付宝

如果本文帮助到您,请随意打赏作者