上一文章我们学习了委托,今天我们来学习事件。事件的很多部分和委托类似,看起来,委托就像是专门用于某种特殊用途的委托。
如果没有委托,就事件就无法使用,这就是为什么我们要先学习委托,后学习事件的原因。
事件包含了一个私有的委托,这个委托是事件私有的,我们无法直接调用委托。如下图所示
多说无益,估计图也看不懂,我们还是来看像上一篇文章一样,看看为什么要用事件。
一、为什么要用事件
举个例子,你有一张银行卡,给媳妇用了,你又不放心,偷偷给卡开通了刷卡通知的服务,只要你媳妇一刷卡,银行就给你发条短信,通知你卡被刷了多少钱。
这个刷卡就是一个事件。这个事件一被触发,银行就通知你,你手机上就会显示一条短信。
这其实就是发布者/订阅者模式,也叫观察者模式。
银行是信息的发布者,手机是信息的订阅者。(这台手机当然要在发布者这注册过),当“刷卡”这个事件一被触发,发布者就会通知订阅者执行显示的方法。
有朋友会说,这很简单啊,只要写个IF语句就行啊。
伪代码如下:
If (银行卡.刷卡)
{
我的手机.显示短信()
}
这样分析,我们的程序就要有两个类:银行卡类和手机类。银行卡有个方法:刷卡;手机类有个方法:显示短信。
只要银行卡一执行刷卡的方法,手机就调用显示短信的方法。
这样是可以实现,问题是银行是国家的,手机是你家,银行卡是封装好的类,不可能给你跑到里面去写判断的语句的。全国的手机多了,银行又怎么知道通知你的手机来显示呢?
银行卡类和手机类,本为是完全隔绝的类,是什么让他们联系到一起了呢?
是事件,银行卡对外有这样一个功能,允许用户绑订(注册、订阅)一个手机号。当刷卡事件触发时,就通知注册的手机,显示一条短信。
这样,用户能做的事只有2条:
1、在银行卡上绑订(注册)自己的手机号
2、怕被媳妇发现了,解除手机号和银行卡之间的绑定。
这样的话,就用事件把 银行卡类和手机类联系起来了,同时两个类还是相互独立的。
二、使用事件
要使用事件,拢共要分五步:
□声明一个委托类型。
□发布者声明事件。
□订阅者准备一个方法(事件处理程序,又叫:回调方法)。
□把订阅者的事件处理方法绑定(注册)到事件上。
□发布者触发事件。
1、声明一个委托类型。
这个上篇文章已经学过了。委托类型其实就是一个模板,他规定了委托的返回类型和签名(参数)。这个规定好了,事件才可以按这个规定去创建一个私有的内部委托啊。
2、声明事件。
事件不是类型,事件和方法一样,是类的成员。所以事件只能在类中声明。这一点和委托不同。
delegate void MyDel(); //第一步:声明委托类型
class BankCard //发布者,银行卡类
{
event MyDel MyEvent; //第二步:声明事件
}
3、订阅者准备一个回调方法
这个方法也就是事件处理程序。这个方法必须具有和事件的私有委托相“兼容”(具有相同的返回类型和签名)。
class Phone //订阅者:手机类
{
void Display() //第三步:手机有一个回调方法(事件处理程序)
{
Console.WriteLine("您的银行卡有一笔消费,如非本人操作,请立刻致电本行。");
}
}
4、绑订事件(订阅事件)
把第三步的那个方法绑订到第二步的事件上。
使用+=表达式。
在事件所在类的外部,对事件只能进行+=或-=的操作,不能赋值,不能触发,什么都不能。
BankCard ICBC = new BankCard(); //实例化一个银行卡 工行:-)
Phone IPhone = new Phone(); //实例化一个手机
ICBC.MyEvent += IPhone.Display; //第四步:把订阅者(手机)的事件处理方法(显示)绑订到事件上。
5、触发事件。
触发事件只能在事件所以类的内部触发。因为事件对外只有两个操作+=和-=,除此之外,不能进行其他的操作,所以不能赋值,也不能调用。
举个例子,金库的防盗报警器,只有在金库进了贼,才能在金库内部触发报警事件,发送报警信息给110。而110或路人不能在外部触发报警事件的。哪怕110想测试下报警功能,也是能安排金库的人在内部人为触发报警事件,在外部不能触发事件。也不能修改报警器,能做的只是给报警器绑定(或移除)几个接收通知的手机而已。
我们在银行卡类内写个刷卡的方法,这个方法可以在类的内部触发事件。
触发事件和调用方法一样,只是事件的参数列表必须和事件的私有委托类型一致,也就是我所谓的“委托、事件、回调方法要相兼容”
public void PayByCard() //第五步:触发的方法:刷卡,一刷卡就触发事件
{
MyEvent(); //触发事件
}
下面贴上完整的代码
namespace event_1
{
delegate void MyDel(); //第一步:声明委托类型
class BankCard //发布者,银行卡类
{
public event MyDel MyEvent; //第二步:声明事件
public void PayByCard() //第五步:触发的方法:刷卡,一刷卡就触发事件
{
MyEvent(); //触发事件
}
}
class Phone //订阅者:手机类
{
public void Display() //第三步:订阅者的回调方法(事件处理程序)
{
Console.WriteLine("您的银行卡有一笔消费,如非本人操作,请立刻致电本行。");
}
}
class Program
{
static void Main(string[] args)
{
BankCard ICBC = new BankCard(); //实例化一个银行卡 工行:-)
Phone IPhone = new Phone(); //实例化一个手机
ICBC.MyEvent += IPhone.Display; //第四步:把订阅者(手机)的回调方法(显示)绑订到事件上。
ICBC.PayByCard(); //最后:执行刷卡操作
}
}
}
运行结果如下:
三、事件的具体应用
以上通过一个简单的例子,示范了事件的构成、声明及使用。
太简单了,以至于只通知了刷卡这件事,用户却不知道刷卡的具体情况,如:消费了多少钱?余额还剩多少?特别是我的手机已经绑定了N家银行的银行卡,我又如何才能知道这是哪个银行卡发来的信息呢?
下面我们就来完善这个类。
首先,我们的银行卡类要有2个属性,名称和余额。刷卡的方法要有一个Int参数,我们好知道媳妇到底败了多少家。
其次,我们的事件要把这“银行名称”、“刷卡金额”和“余额”这三个银行卡的参数传值给手机的回调方法。这就需要事件内部的委托,要有这三个参数。
好吧,看代码,这次我们贴上完整的代码。
namespace event_1
{
//第一步:声明委托类型
delegate void PayDel(string bankName, int balance, int amount);
//发布者:银行卡类
class BankCard
{
public string Name { set; get; } //声明一个属性:银行卡的名称
public int Balance { set; get; } //声明一个属性:银行卡的余额
public event PayDel PayEvent; //声明刷卡事件
public void PayByCard(int amount) //触发的方法:刷卡,一刷卡就触发事件
{
this.Balance -= amount; //刷卡时,在余额中减去刷卡金额
if (PayEvent!=null) //确认事件不为空(事件中有方法绑定)
{
PayEvent(this.Name, this.Balance, amount); //触发事件
}
}
}
//订阅者:手机类
class Phone
{
public void Display(string Name,int balance,int amount) //回调方法:显示信息
{
Console.WriteLine("短信:您的{0}银行卡消费{1}元,当前余额{2}元。",Name,amount,balance);
}
}
class Program
{
static void Main(string[] args)
{
BankCard ICBC = new BankCard {Name="工商银行",Balance=5000 }; //实例化一个银行卡 工商银行,余额5000元
Phone IPhone = new Phone(); //实例化一个手机
ICBC.PayEvent += IPhone.Display; //把订阅者(手机)的事件处理方法(显示)绑订到事件上。
ICBC.PayByCard(1000); //执行刷卡操作
}
}
}
运行结果如下:
当你媳妇刷卡1000元时,银行卡的余额5000元变成了4000元。同时触发了PayEvent事件,事件命令它内部的私有委托PayDel执行,PayDel委托将三个参数传给了手机,手机调用和委托相“兼容”的Display回调方法显示出了短信内容。
我做了详尽的注释,相信代码很易懂。
现在问题来了,有时手机会没电,怕漏接了短信,能不能再注册一个电子邮箱,媳妇败家时,给邮箱也发封邮件呢?
最好也给媳妇的手机也发个短信,也给她敲敲警钟?
很容易,只要我们把邮箱类的收取信件的回调方法绑定到PayEvent事件上,把媳妇手机也绑上就OK了。
namespace event_1
{
//声明委托类型
delegate void PayDel(string bankName, int balance, int amount);
//发布者:银行卡类
class BankCard
{
public string Name { set; get; } //银行卡的名称
public int Balance { set; get; } //银行卡的余额
public event PayDel PayEvent; //声明刷卡事件
public void PayByCard(int amount) //触发的方法:刷卡,一刷卡就触发事件
{
this.Balance -= amount; //刷卡时,在余额中减去刷卡金额
if (PayEvent!=null) //确认事件不为空(事件中有方法绑定)
{
PayEvent(this.Name, this.Balance, amount); //触发事件
}
}
}
//订阅者:手机类
class Phone
{
public void Display(string Name,int balance,int amount) //回调方法:显示信息
{
Console.WriteLine("短信:您的{0}银行卡消费{1}元,当前余额{2}元。",Name,amount,balance);
}
}
//新加的代码 订阅者:电子邮箱类
class Email
{
public void GetMail(string Name, int balance, int amount) //回调方法:收件
{
Console.WriteLine("邮件:您的{0}银行卡于今日消费{1}元,当前余额{2}元。 [发信人:{0}]", Name, amount, balance);
}
}
class Program
{
static void Main(string[] args)
{
BankCard ICBC = new BankCard {Name="工商银行",Balance=5000 }; //实例化一个银行卡 工商银行,余额5000元
Phone IPhone = new Phone(); //实例化一个手机 我的手机
//新加的代码
Phone IPhone6Plus = new Phone(); //实例化一个手机,媳妇的6Plus
Email SinaMail = new Email(); //实例化一个电子邮箱
ICBC.PayEvent += IPhone.Display; //把订阅者(手机)的事件处理方法(显示)绑订到事件上。
//新加的代码
ICBC.PayEvent += SinaMail.GetMail; //把订阅者(邮箱)的事件处理方法(收件)绑订到事件上。
ICBC.PayEvent += IPhone6Plus.Display; //把订阅者(媳妇的手机)的事件处理方法(显示)绑订到事件上。
ICBC.PayByCard(1000); //执行刷卡操作
}
}
}
运行结果:
very nice!
四、小节
事件上不是类型,事件是类的成员。由于事件是成员:我们不能在一段可执行代码中声明事件;它必须声明在类或结构中,和其他成员一样。(《C#图解教程》第4版P258)
订阅(绑定)使用+=运算符,取消订阅(移除)使用-=运算符,看起来就像是在通过+=和-=运算符使用委托类型的字段。但这个过程实际是在调用方法(add和remove方法)。对于一个纯粹的事件,你所能做的事情就是订阅(添加一个事件处理程序)或取消订阅(删除一个事件处理程序)。最终是由事件方法来做真正有用的事情。(《深入理解C#》第3版p32)
事件不是委托实例,只是成对的add/remove方法(类似于属性的set/get方法)。(《深入理解C#》第3版p33)
事件、事件私有的委托、回调方法必须“兼容”。
相信你已经初步理解了事件的概念。
慢着,事件讲完了?为什么我们WinForm里经常看到的事件代码:
private void button1_Click(object sender, EventArgs e)
{
}
我还是看不懂?没事,请看 【菜鸟学c#】委托和事件(二、标准事件)。