一. 理解委托
面向对象的核心思想之一就是将数据和操作封装成一个整体,一般定义为类(关键字Class)。类也是一种数据类型,等同于int,string,char等常见数据类型。
常见的都是把int,string,char等常见数据类型作为函数形参,如:
void Add(int a, int b);
那么函数能作为形参吗?答案是可以的,当函数作为形参时,此函数就称为回调函数。在C#中使用了一种更方便的方式——委托,使函数作为形参更方便。
委托是一种特殊的引用类型,将方法作为特殊的对象封装起来,从而将方法作为变量或参数进行传递。
委托二要素:
- 返回值类型;
- 函数的特征标(形参类型、形参顺序、形参个数);
可以将委托看成一种模具,凡是能够完全匹配此模具的函数都可以放进此模具中当成参数或变量来使用。
二. 委托的使用
1. 委托的使用过程:
1)定义委托类型;
定义委托时使用关键字delegate,如:
public delegate void Del(int a,int b); 此委托就定义了一种返回值是void,特征标是(int,int)的函数引用类型;
2)创建此委托类型的对象,将方法绑定到此委托上;
如果定义变量 Del function=new Del(Add),
3)通过委托调用方法;
那么function(1,2)就相当于调用了Add(1,2);
2. 委托可以进行加减运算,如定义如下函数
void Add1(int a, int b);
void Add2(int a, int b);
void Add3(int a, int b);
Del function1=new Del(Add1);
Del function2=new Del(Add2)
Del function3=function1+function2;
则function3(1,2)就会依次调用Add1(1,2)和Add2(1,2);
如果再进行function3-=function1,则function3(1,2)就会只会调用Add2(1,2);
三. 委托与事件
在使用委托的过程中可能存在这种情况:
void Add1(int a, int b);
void Add2(int a, int b);
void Add3(int a, int b);
Del function1=new Del(Add1);
Del function2=new Del(Add2)
Del function3+=function1;
function3+=function2;
此时若再写function3=Add3,那么再调用function3(1,2)就只会调用Add3(1,2),而不会调用Add1和Add2函数。为了避免出现将+=错写成=的情况,可以将委托封装成事件来实现可靠的发布订阅。
具体做法是在委托类型前添加关键字event来定义此委托的事件,如:
public event Del EventDel; 就将委托类型Del封装成了事件EventDel;
此时可以使用将函数订阅到事件上,当激发事件时就会调用此事件所订阅的函数,如:
EventDel+=Add1;
EventDel+=Add2;
EventDel+=Add3;
则EventDel(1,2)就会依次调用函数Add1、Add2和Add3。
一般使用委托和事件的可靠做法是先判断此委托或事件不为null,然后再调用。
如:
if( null != EventDel )
{
EventDel(1,2);
}
1. 委托发布者订阅者模式步骤:
1)首先定义委托类型,发布类中定义一个公有的委托成员和激发委托的方法;
2)订阅者类中定义委托处理方法,其中委托处理方法与委托类型的返回值和形参列表都要一致;
3)将订阅者对象的委托处理方法绑定(注册)到发布者对象的委托成员上;
4)当发布者对象激发委托操作,就会自动调用订阅者对象的委托处理方法;
2. 事件发布者订阅者模式步骤:
1)首先定义委托类型,event和委托一起构成事件类型就可以定义事件;
2.)在发布类中创建事件公有成员和触发事件的方法;
3)订阅者类中定义事件处理方法,其中事件处理方法与委托类型的返回值和形参列表都要一致;
4)将订阅者对象的事件处理方法绑定(注册)到发布者对象的公有事件成员上;
5)当发布者对象触发事件操作,就会自动调用订阅者对象的事件处理方法;
四. 发布者订阅者的总结
委托前加上event修饰符就变成了事件
在发布类中定义公有事件对象,
只能在发布类对象中触发事件,不能在订阅者对象中触发事件;
函数可以绑定同类事件,也可以绑定跨类事件。
发布者只管发出触发事件,不管具体响应,类似广播只管广播不管谁收听。谁订阅事件,谁就是订阅者,谁就响应且根据自己的特征情况安排响应操作,即响应操作在各个订阅者的内部实现。各个订阅者对注册的同一发布者的触发事件的响应操作方法相互独立互不干扰,各自处理各自的响应操作。只是有一个要求,各个订阅者的响应操作的返回值和形参列表要与所对应的发布者的触发事件委托的返回值和形参列表要完全一致。返回值和形参列表不同则表示事件与其他事件的不同,响应操作与其他响应操作的不同;一类发布者的事件就对应一类订阅者的响应操作方法。形参列表就是发布者的事件与其对应的订阅者的响应操作方法相互传递信息进行联系的渠道和纽带。发布者的事件注册多个订阅者的事件处理方法后,当发布者触发事件时会依次调用执行所注册的订阅者的事件处理方法,默认时同步执行的。
五. 例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PublisherSubscriber
{
public delegate int Getstunum(int num);
public delegate void SendCmd(string cmd);
class SchoolCmd
{
private string name;
public event Getstunum EventGetstunum; /// <summary>
/// 公有事件成员
/// </summary>
public event SendCmd EventSendCmd; /// <summary>
/// 公有事件成员
/// </summary>
/// <param name="s