事件的声明
事件的声明有两种方式,一种是完整声明,一种是简略声明(filed-like)
回顾一下,事件模型的五个组成部分:事件的拥有者(sender),事件成员(event),事件的响应者(event subscriber),事件处理器(event handler),事件的订阅
补充:事件或者委托可以用来两个窗口之间传递所需数据!!!
完整声明
下面是事件的完整声明
。
//首先定义一个委托(使用EventHandler后缀是为了表明此委托是用于处理事件的)
public delegate void OrderEventHandler(Coustomer coustomer,OrderEventArgs e);
//以及处理委托需要的事件参数
public class OrderEventArgs:EventArgs
{
public string DishName { get; set; }
public double Size { get; set; }
}
//声明一个顾客类(事件的拥有者)
public class Coustomer
{
//声明委托类型字段
private OrderEventHandler orderEventHandler;
//声明事件
private event OrderEventHandler Order
{
//事件处理器的添加器
add
{
this.orderEventHandler += value;
}
//事件处理器的移除器
remove
{
this.orderEventHandler -= value;
}
}
private double bill;
public double Bill { get => bill; set => bill = value; }
public void PlayTheBill()
{
Console.WriteLine("i will pay ${0}",this.Bill);
}
}
是不是感觉很熟悉,没错,事件就是对委托字段的封装,类比下面的bill字段封装后的属性一样。
这时候,顾客拥有了事件,也就是事件模型的事件拥有者,事件成员已经有了,接下来构建事件响应者和事件处理器。
//声明服务员类(事件的响应者)
public class Waiter
{
//声明事件处理器
public void Action(Coustomer coustomer, 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;
}
coustomer.Bill += price;
}
}
接下来开始事件的订阅:
class Program
{
static void Main(string[] args)
{
Coustomer coustomer = new Coustomer();
Waiter waiter = new Waiter();
//事件的订阅
coustomer.Order += waiter.Action;
}
}
事件模型的五部分已经完成了,而这时候,需要顾客去主动点餐才会触发这些事件,因此在顾客类中添加点菜函数:
//开始点菜
public void Action()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("thinking....");
}
//判断不为空(如果没人订阅你这个事件,那么不执行)
//以这个例子举例,如果没有服务员订阅顾客的点餐事件,这时候就不触发事件
if (this.orderEventHandler != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "yu";
e.Size = "small";
//this.orderEventHandler.Invoke(this, e);//委托调用的两种方式
this.orderEventHandler(this, e);//触发事件
}
}
这时候全部逻辑已经完成,主函数代码加入点菜即可
class Program
{
static void Main(string[] args)
{
Coustomer coustomer = new Coustomer();
Waiter waiter = new Waiter();
coustomer.Order += waiter.Action;
coustomer.Action();
coustomer.PlayTheBill();
Console.ReadLine();
}
}
结果:
这就是事件的完整声明和调用。
事件的简略声明
书接上回,我们在顾客点餐的函数中,把委托字段改为事件,会发生什么呢?
可以看到,事件只能出现在-=和+=操作符的左边。
接下来我们声明事件,删掉声明的委托字段和完整声明事件代码,然后添加简略声明事件代码
//简略声明事件
public event OrderEventHandler Order;
这时候我们得修改Action函数,因为已经没有orderEventHandler这个委托字段了,但我们这时候如何激活事件呢?这时候就要把orderEventHandler换成Order这个事件。
这时候就会发现,好像前后矛盾了?没错,这是微软在设计这个语法糖的时候造成的一个矛盾。这样会让人认为事件就是字段,然而并不是,前面的例子已经很好的说明了事件并不是字段,但这是怎么回事呢?
使用反编译器ildasm打开我们项目的exe文件
可以看到在Coustomer中有我们的事件Order,然后在看到上面两个水蓝色的方块,这是字段,一个是我们自己声明的bill字段,而另外一个是编译器帮我们生成的Order委托字段,也就是说,委托类型的字段是存在的,只是没有显示在我们的代码里面!
为什么需要事件
当我们把声明事件改为声明委托
public event OrderEventHandler Order; ==> public OrderEventHandler Order;
这时候你会发现程序仍然能运行,也就是说,我们完全可以直接用委托,那我们为什么还需要事件???
因为存在安全隐患,前面我们得知,事件只能用+=或-=操作符,但对于委托而已,委托是可以用invoke方法来调用的,因此可能会出现“借刀杀人”的情况。例如:
//添加了一个构造方法,方便引用
public class OrderEventArgs:EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
public OrderEventArgs(string DishName, string Size)
{
this.DishName = DishName;
this.Size = Size;
}
}
开始借刀杀人:
class Program
{
static void Main(string[] args)
{
Coustomer coustomer = new Coustomer();
Waiter waiter = new Waiter();
coustomer.Order += waiter.Action;
//coustomer不点餐了
//coustomer.Action();
Coustomer badGuy = new Coustomer();
badGuy.Order += waiter.Action;
//此处开始借刀杀人
badGuy.Order.Invoke(coustomer,new OrderEventArgs("manhanquanxi","large"));
badGuy.Order.Invoke(coustomer, new OrderEventArgs("fish", "large"));
badGuy.PlayTheBill();
coustomer.PlayTheBill();
Console.ReadLine();
}
}
结果:
可以看到,因为委托可以用invoke调用,因此产生了这种闹剧,如果使用事件则不会发生。
总结
非常感谢刘铁猛老师的课程,让我对事件和委托有了非常深刻的理解,本博客是基于老师的视频提取出来的一些结论,方便后来人学习时更加方便,如果看了本博客仍不太明白,建议观看老师的视频
刘老师 youtube:https://www.youtube.com/channel/UCmvAggiJGwJGSzAtqFRBQ7g
b站视频:https://www.bilibili.com/video/BV1wx411K7rb?p=22