一. 理解委托
面向对象的核心思想之一就是将数据和操作封装成一个整体,一般定义为类(关键字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="str"></param>
public SchoolCmd(string str)
{
name = str;
}
public void OnGetstunum( int num)
{
if(null != EventGetstunum)
{
EventGetstunum(num); /触发事件
}
}
public void OnSendCmd(string cmd)
{
if(null != EventSendCmd)
{
EventSendCmd(cmd);
}
}
}
class Teacher
{
private string name;
public Teacher(string str)
{
name = str;
}
定义事件处理方法
public int Calstunum(int n)
{
int sum=0;
for(int i=0; i<n;i++)
{
sum += i;
}
Console.Write(name + "老师: " + "学生人数一共"+sum.ToString()+"人 \r\n");
return sum;
}
public void DoasCmd(string cmd)
{
if(cmd.Equals("卫生"))
{
Console.Write(name + "老师: " + "开始组织学生打扫卫生 钦差兼督军 \r\n");
}
else if (cmd.Equals("上课"))
{
Console.Write(name + "老师: " + "准备上课,先偷偷看看哪个学生在偷懒 \r\n");
}
else if (cmd.Equals("下课"))
{
Console.Write(name + "老师: " + "下课了,还要批改作业!!!不知自己教的学生水平到底如何 \r\n");
}
else if (cmd.Equals("考试"))
{
Console.Write(name + "老师: " + "考考学生,其乐无穷 \r\n");
}
else if (cmd.Equals("检查"))
{
Console.Write(name + "老师: " + "领导要检查了,组织好学生,紧张。。。要让领导满意,学生也满意,家长更满意,周末要把暖气烧的旺 \r\n");
}
else if (cmd.Equals("放假"))
{
Console.Write(name + "老师: " + "放假了,可以好好休息一下了 \r\n");
}
else
{
;
}
}
}
class Student
{
private string name;
public Student(string str)
{
name = str;
}
定义事件处理方法
public int Calstunum(int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += 2*i;
}
Console.Write("学生"+name + ": " + "学生有" + sum.ToString() + "人 \r\n");
return sum;
}
public void DoasCmd(string cmd)
{
if (cmd.Equals("卫生"))
{
Console.Write("学生" + name + ": " + "打扫卫生,灰头土脸了 \r\n");
}
else if (cmd.Equals("上课"))
{
Console.Write("学生" + name + ": " + "要上课了,不能玩了 \r\n");
}
else if (cmd.Equals("下课"))
{
Console.Write("学生" + name + ": " + "下课了,最想听到的消息!!! \r\n");
}
else if (cmd.Equals("考试"))
{
Console.Write("学生" + name + ": " + "又要考试了,学的全会,蒙的全对 \r\n");
}
else if (cmd.Equals("检查"))
{
Console.Write("学生" + name + ": " + "检查,检查,这么多检查\r\n");
}
else if (cmd.Equals("放假"))
{
Console.Write("学生" + name + ": " + "放假了,我要去游泳,顺便摸条大鱼 \r\n");
}
else
{
;
}
}
}
}
static void Main(string[] args)
{
SchoolCmd SCmd = new SchoolCmd("育才中学"); /发布者对象
Teacher tc = new Teacher("诸葛熔炉"); 订阅者对象
Teacher tc1 = new Teacher("欧阳无敌"); 订阅者对象
Teacher tc2 = new Teacher("司马无情"); 订阅者对象
Teacher tc3 = new Teacher("上官无敌"); 订阅者对象
Teacher tc4 = new Teacher("东方不败"); 订阅者对象
Teacher tc5 = new Teacher("西门吹雪"); 订阅者对象
Teacher tc6 = new Teacher("尉迟鱼鱼"); 订阅者对象
Teacher tc7 = new Teacher("慕容嘘嘘"); 订阅者对象
Student stu = new Student("石头"); 订阅者对象
Student stu1 = new Student("铁蛋"); 订阅者对象
Student stu2 = new Student("贾宝玉"); 订阅者对象
Student stu3 = new Student("范进"); 订阅者对象
Student stu4 = new Student("嘎鱼嘴"); 订阅者对象
Student stu5 = new Student("鲶鱼头"); 订阅者对象
Student stu6 = new Student("菠萝"); 订阅者对象
Student stu7 = new Student("爱飞翔的小柿子"); 订阅者对象
Student stu8 = new Student("不想动的蚯蚓"); 订阅者对象
Student stu9 = new Student("买买买"); 订阅者对象
SCmd.EventGetstunum += new Getstunum(tc.Calstunum);
SCmd.EventGetstunum += new Getstunum(stu.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc.DoasCmd);
SCmd.EventSendCmd += new SendCmd(stu.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc1.Calstunum);
SCmd.EventGetstunum += new Getstunum(stu1.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc1.DoasCmd);
SCmd.EventSendCmd += new SendCmd(stu1.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc2.Calstunum);
SCmd.EventGetstunum += new Getstunum(stu2.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc2.DoasCmd);
SCmd.EventSendCmd += new SendCmd(stu2.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc3.Calstunum);
SCmd.EventGetstunum += new Getstunum(stu3.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc3.DoasCmd);
SCmd.EventSendCmd += new SendCmd(stu3.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc4.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc4.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc5.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc5.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc6.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc6.DoasCmd);
SCmd.EventGetstunum += new Getstunum(tc7.Calstunum);
SCmd.EventSendCmd += new SendCmd(tc7.DoasCmd);
SCmd.EventGetstunum += new Getstunum(stu4.Calstunum);
SCmd.EventSendCmd += new SendCmd(stu4.DoasCmd);
SCmd.EventGetstunum += new Getstunum(stu5.Calstunum);
SCmd.EventSendCmd += new SendCmd(stu5.DoasCmd);
SCmd.EventGetstunum += new Getstunum(stu6.Calstunum);
SCmd.EventSendCmd += new SendCmd(stu6.DoasCmd);
SCmd.EventGetstunum += new Getstunum(stu7.Calstunum);
SCmd.EventSendCmd += new SendCmd(stu7.DoasCmd);
SCmd.EventGetstunum += new Getstunum(stu8.Calstunum);
SCmd.EventSendCmd += new SendCmd(stu8.DoasCmd);
SCmd.EventGetstunum += new Getstunum(stu9.Calstunum);
SCmd.EventSendCmd += new SendCmd(stu9.DoasCmd);
/调用发布者对象函数以触发事件
SCmd.OnGetstunum(34);
SCmd.OnSendCmd("卫生");
SCmd.OnSendCmd("上课");
SCmd.OnSendCmd("下课");
SCmd.OnSendCmd("考试");
SCmd.OnSendCmd("检查");
SCmd.OnSendCmd("放假");
}