事件的声明
·完整声明
·简略声明(字段式声明,field-like)
有了委托字段/属性,为什么还需要事件?
`为了程序的逻辑更加“有道理”,更加安全,谨防“借刀杀人”!
所以事件的本质是委托字段的一个包装器
·这个包装器对委托字段的访问起限制作用,相当于一个“蒙版”
·封装(encapsulation)的一个重要作用是隐藏
·事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
·添加/移除事件处理器的时候可以直接使用方法名,这是委托实例所不具备的功能
例1:声明事件的完整格式
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp42
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();//事件的拥有者
Waiter waiter = new Waiter();//事件的响应者
customer.Order += waiter.Action;//事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs: EventArgs//事件信息命名规则,继承自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);/// <summary>
/// 为什么要使用EventHandler这个后缀呢
/// 1.让别人一看就明白这是专门用来声明事件的
/// 2.用来约束事件处理器
/// 3.这个委托未来创建出来的实例是专门用来存储事件处理器的
/// </summary>
public class Customer //加入public是为了保证访问级别一致
{
private OrderEventHandler orderEventHandler;//声明一个委托字段,用来引用、存储事件处理器
public event OrderEventHandler Order//事件成员
{
add
{
this.orderEventHandler += value;//事件处理器的添加器,value代表传进来的eventhandler
}
remove
{
this.orderEventHandler -= value;//事件处理器的移除器87
}
}
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine( "I will pay ${0}",this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think ...");
Thread.Sleep(1000);
}
if (this.orderEventHandler!=null)//没有事件处理器订阅你的事件时候会出异常,所以要判断是否为空
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this, e);//间接调用Waiter
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
public void Action(Customer customer, OrderEventArgs e)//事件的处理器
{
Console.WriteLine("I will server you the dish -{0}.",e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
例二——事件的简单声明
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp42
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();//事件的拥有者
Waiter waiter = new Waiter();//事件的响应者
customer.Order += waiter.Action;//事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs: EventArgs//事件信息命名规则,继承自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);/// <summary>
/// 为什么要使用EventHandler这个后缀呢
/// 1.让别人一看就明白这是专门用来声明事件的
/// 2.用来约束事件处理器
/// 3.这个委托未来创建出来的实例是专门用来存储事件处理器的
/// </summary>
public class Customer //加入public是为了保证访问级别一致
{
public event OrderEventHandler Order;//事件的简单声明,相当于事件里面包括了一个委托字段
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine( "I will pay ${0}",this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think ...");
Thread.Sleep(1000);
}
if (this.Order !=null)//没有事件处理器订阅你的事件时候会出异常,所以要判断是否为空
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);//用了事件代替,语法糖
///再次强调:
///在customer类内部能够使用Order事件去做非空比较以及调用Order.Invoke方法纯属不得已而为之。
///因为事件的简化声明时,我们没有手动声明一个委托类型的字段。这是微软编译器语法糖所造成的语法冲突和前后不一致
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
public void Action(Customer customer, OrderEventArgs e)//事件的处理器
{
Console.WriteLine("I will server you the dish -{0}.",e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
用于声明事件的委托类型的命名约定
·用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)
·FooEventHandler委托的参数一般有两个(由win32 API演化而来,历史悠久)
·第一个是object类型,名字为sender,实际上就是事件的拥有者、事件的source
`第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数为e。也就是前面说的事件参数
·虽然没有官方的说法,但我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息”
·触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”
·访问级别为protected,不能为public,不然又成为了可以“借刀杀人”了
例三——“借刀杀人”
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp42
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();//事件的拥有者
Waiter waiter = new Waiter();//事件的响应者
customer.Order += waiter.Action;//事件的订阅
//customer.Action();
OrderEventArgs e = new OrderEventArgs();
e.DishName = "manhanquanxi";
e.Size = "large";
OrderEventArgs e2 = new OrderEventArgs();
e2.DishName = "beer";
e2.Size = "large";
Customer badGuy = new Customer();
badGuy.Order += waiter.Action;//order此时已经不是一个事件而是一个委托字段了
badGuy.Order.Invoke(customer,e);//把账记在另一个客人头上
badGuy.Order.Invoke(customer, e2);
customer.PayTheBill();
}
}
public class OrderEventArgs: EventArgs//事件信息命名规则,继承自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class Customer //加入public是为了保证访问级别一致
{
public OrderEventHandler Order;//去掉event
/// <summary>
/// 小结
/// 如果不加event,order就不再是一个事件,而是作为一个委托字段
/// 成了委托字段之后order就可以被随意invoke,而且可以这笔账记在其他人的头上
/// 会造成程序混乱,就像是C++的指针一样
/// 加入的event以后,order作为事件只能出现在-=或者+=旁边,安全了许多
/// </summary>
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine( "I will pay ${0}",this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think ...");
Thread.Sleep(1000);
}
if (this.Order !=null)//没有事件处理器订阅你的事件时候会出异常,所以要判断是否为空
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);//用了事件代替,语法糖
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
public void Action(Customer customer, OrderEventArgs e)//事件的处理器
{
Console.WriteLine("I will server you the dish -{0}.",e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
例四–省去委托声明使用微软准备好的事件声明方式
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp42
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();//事件的拥有者
Waiter waiter = new Waiter();//事件的响应者
customer.Order += waiter.Action;//事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs: EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
// public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
//可以声明,但是没有必要
public class Customer //加入public是为了保证访问级别一致
{
public event EventHandler Order;
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine( "I will pay ${0}",this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think ...");
Thread.Sleep(1000);
}
if (this.Order !=null)//没有事件处理器订阅你的事件时候会出异常,所以要判断是否为空
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);
//这里还不够完美
//格式不规范
//一个方法做了两件事情,违反了面向对象的规则
//另外只能固定点“宫保鸡丁大份”不够灵活,最好能够外部决定我要点什么
//修改版见例五
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
private Customer sender;
public void Action(object sender, EventArgs e)//事件的处理器
{
Customer customer = sender as Customer;
OrderEventArgs orderInfo = e as OrderEventArgs;
Console.WriteLine("I will server you the dish -{0}.",orderInfo.DishName);
double price = 10;
switch (orderInfo.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
例五——修改版
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp42
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();//事件的拥有者
Waiter waiter = new Waiter();//事件的响应者
customer.Order += waiter.Action;//事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs: EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
// public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
//可以声明,但是没有必要
public class Customer //加入public是为了保证访问级别一致
{
public event EventHandler Order;
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine( "I will pay ${0}",this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit down.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think ...");
Thread.Sleep(1000);
}
this.Onorder("kongbaojidinng","large");
}
protected void Onorder(string disName, string Size)
{
if (this.Order != null)//没有事件处理器订阅你的事件时候会出异常,所以要判断是否为空
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = disName;
e.Size = Size;
this.Order.Invoke(this, e);//this是事件拥有者
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
public void Action(object sender, EventArgs e)//事件的处理器
{
Customer customer = sender as Customer;
OrderEventArgs orderInfo = e as OrderEventArgs;
Console.WriteLine("I will server you the dish -{0}.",orderInfo.DishName);
double price = 10;
switch (orderInfo.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
事件的命名约定
·带有时态的动词或动词短语
·事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时
事件与委托的关系
Q:事件真的是“以特殊方式声明的委托字段/实例”吗?
A:
1.不是!只是声明的时候"看起来像"(对比委托字段与事件的简单声明)
2.事件声明的的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键字则更像一个修饰符——这就是错觉的来源
3.订阅事件的时候+=操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源
例customer.order += waiter.Action;可以写成 customer order += new EventHandler(waiter.Action);
4.重申:事件的本质是加装在委托字段上的一个“蒙版”(mask),是个起掩蔽作用的包装器。这个用于阻挡非法操作的“蒙版”绝对不是委托字段本身
Q:为什么要使用委托类型来声明事件?
A:
1.站在source的角度来看,是为了表明source能够对外传递哪些消息
2.站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件
3.委托类型的实例将用于储存(引用)事件处理器
对比事件和属性
1.属性不是字段—很多时候属性是是字段的包装器,这个包装器用来保护字段不被滥用
2.事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被滥用
3.包装器永远都不可能是被包装的东西