事件的声明
- 事件的声明
- 完整声明
事件是基于委托的(两层意思):- 第一层:事件需要委托类型来做一个约束,这个约束既规定了事件能发送什么样的消息给事件的响应者,也规定了事件响应者能收到什么样的消息。这就决定了事件响应者的事件处理器必须能够和这个约束匹配上,它才能够订阅这个事件。
- 第二层:当事件的响应者向事件的拥有者提供了能够匹配这个事件的事件处理器之后,需要有个地方把这个事件处理器保存或记录下来。能够记录/引用方法的任务只有委托类型的实例才能做到,所以我们用到了委托。
- 总结:事件这种成员无论从表层约束来讲,还是底层实现来讲,它都依赖于委托类型。
- 简略声明
- 完整声明
事件声明完整格式:
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
//使用Acion方法作为Order事件的事件处理器(Action后面不跟括号)
customer.Action();
//由事件拥有者的内部逻辑触发事件
//在customer.Action调用Think方法中,customer.Order触发
//waiter.Action根据customer.Order的参数做出响应
customer.PayTheBill();
}
}
//传递事件消息
public class OrderEventArgs : EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
//声明委托类型
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
//EventHandler后缀:
//1.别人看到后,就知道这个委托是专门用来声明事件的;
//2.表明这个委托是用来约束事件处理器的;
//3.表明这个委托未来创建出来的实例,是专门用来存储事件处理器的。
public class Customer //事件拥有者
{
private OrderEventHandler orderEventHandler;
//这个委托字段用来存储/引用那些事件处理器
//声明事件
public event OrderEventHandler Order
{
add //事件处理器的添加器
{
this.orderEventHandler += value;
//+=后面跟外面传进来的EventHandler,这里用上下文关键字value
}
remove
{
this.orderEventHandler -= value;
}
}
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);
//点菜这一行为是Customer自己,所以这里用this
}
}
//一连串地调用WalkIn、SitDown、Think
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 serve 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;
//客户点的菜
}
}
事件简略声明:
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 event OrderEventHandler Order;
//拿委托类型OrderEventHandler作为事件的约束,以及存储对事件处理器的引用
//事件的名字Order
//看起来像是委托类型的字段,用event关键字修饰了一下,但不是委托
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)
//没有手动声明的委托字段了,只能使用事件名Order来进行判断和调用
{
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 serve 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;
}
}
- 有了委托字段/属性,为什么还需要事件?
- 为了程序的逻辑更加“有道理”、更加安全,谨防“借刀杀人”
- 所以事件的本质是委托字段的一个包装器
- 这个包装器对委托字段的访问起限制作业,相当于一个“蒙版”
- 封装(encapsulation)的一个重要功能就是隐藏
- 事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
- 用于声明事件的委托类型的命名约定
- 用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)
- FooEventHandler委托的参数一般有两个(由Win32API演化而来,历史悠久)
- 第一个是object类型,名字为sender,实际上就是事件的拥有者、事件的source。
- 第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数为e。也就是前面说的事件参数。
- 虽然没有官方的说法,但我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息”。
- 触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”
- 访问级别为protected,不能为public,不然又成了可以“借刀杀人”了
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 event EventHandler Order;
//使用.NET已经准备好的EventHandler委托
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("Gongpao Chicken", "large");
}
//触发Order事件的方法
protected void OnOrder(string dishName,string size) //可以从方法外部能够决定点什么菜、菜的分量
{
if (this.Order != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = dishName;
e.Size = size;
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}
}
public class Waiter
{
public void Action(object sender, EventArgs e)
//EventHandler委托的2个默认参数,object、EventArgs
{
//类型转换
Customer customer = sender as Customer;
OrderEventArgs orderInfo = e as OrderEventArgs;
Console.WriteLine("I will serve 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;
}
}
- 事件的命名约定
- 带有时态的动词或者动词短语
- 事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时
事件与委托的关系
- 事件真的是“以特殊方式声明的委托字段/实例”吗?
- 不是!只是声明的时候“看起来像”(对比委托字段与事件的简化声明,field-like)
- 事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键字则更像是一个修饰符——这就是错觉的来源之一
- 订阅事件的时候+=操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也是让事件看起来像是一个委托字段——这是错觉的又一来源
- 重申:事件的本质是加装在委托字段上的一个“蒙版”(mask),是个起掩蔽作用的包装器。这个用于阻挡非法操作的“蒙版”绝不是委托字段本身
- 为什么要使用委托类型来声明事件?
- 站在source的角度来看,是为了表明source能对外传递哪些消息
- 站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样的签名的方法来处理(响应)事件
- 委托类型的实例将用于存储(引用)事件处理器
- 对比事件与属性
- 属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
- 事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被滥用
- 包装器永远都不可能是被包装的东西