北京市保障性住房建设投资中心网站6,网站建设php实验报告,中企动力网站后台 好用吗,网站建设好了怎么做推广目录 1、事件模型的5个组成部分2、使用内置委托类型声明事件2.1 EventHandler2.1.1 #xff1f;2.1.2 this2.1.3 使用匿名函数和lamda表达式2.1.3.1 匿名函数2.1.3.2 lamda表达式 2.1.4 异常处理 2.2 EventHandlerTEventArgs 3、使用自定义委托类型声明事件3.1 事件的… 目录 1、事件模型的5个组成部分2、使用内置委托类型声明事件2.1 EventHandler2.1.1 2.1.2 this2.1.3 使用匿名函数和lamda表达式2.1.3.1 匿名函数2.1.3.2 lamda表达式 2.1.4 异常处理 2.2 EventHandlerTEventArgs 3、使用自定义委托类型声明事件3.1 事件的完整声明3.2 事件的简化声明 4、委托和事件的区别5、事件的作用 事件event让类或对象具备在某件感兴趣的事发生时通知其他类或对象的能力触发事件的类让事件发生的类叫做发布者publisher处理事件的类叫做订阅者subscriber。 举个例子闻鸡起舞这个成语中有两个对象鸡和祖逖。凌晨1-3点鸡开始第一次的啼叫祖逖听到鸡叫声立马起床开始舞剑。鸡是事件的发布者当时间到了凌晨1-3点时鸡叫事件发生了而祖逖的行为和鸡叫这个事件之间存在着订阅关系祖逖在心里告诉自己只要我听到鸡叫声了就赶紧起床舞剑。因此鸡叫时间一发生就会向祖逖发出通知信号祖逖接收到了信号就会采取相应的行动。
需要注意的是
1.祖逖的行为和鸡叫这个事件之间需要有订阅关系。也就是说因为有订阅关系所以鸡叫的发生才会让祖逖舞剑。而如果是狗叫祖逖并不会因此舞剑。2.一个发布者–多个订阅者。不光是祖逖听到鸡叫会起床舞剑邻居家听到鸡叫也可以采取相应行动比如赶集。3.多个发布者–一个订阅者。祖逖不光听到自家的鸡叫起床舞剑听到别人家的鸡叫也会起床舞剑。 1、事件模型的5个组成部分
事件的拥有者event source。也就是Publisher。事件成员event。事件是隶属于发布者的比如公司上市这个事件是隶属于公司主体的没有公司何谈公司上市事件的订阅者响应者。也就是Subscriber。事件处理器event handler。是订阅者的一个成员函数本质上是一个回调函数。所谓函数就是用来执行特定功能的代码块而回调函数就是在程序需要的时候会去调用不需要的时候不去调用的函数。当Publisher没有向Subscriber发通知时回调函数不会执行一旦Publisher向Subscriber发通知回调函数立马执行。事件订阅subscribe。将事件和事件处理器联系起来本质上是一种以委托类型为基础的约定。
2、使用内置委托类型声明事件
.net framework中提供了内置委托类型EventHandler和EventHandlerTEventArgs。EventHandler用来声明不包含事件参数的事件EventHandlerTEventArgs用来声明包含事件参数的事件。事件参数是事件发生时发布者会向订阅者发送的数据。比如闻鸡起舞这个事件的发生鸡并没有向祖逖发送任何信息发送了也听不懂哈哈因为鸡叫本身已经能说明所有问题不需要额外的信息了。再比如手机收到信息了手机铃声响这个事件会向人发送通知的同时还会发送信息事件参数铃声只是提醒人看手机信息才是关键所以在这个事件中需要有事件参数。
2.1 EventHandler
namespace ConsoleApp1
{class Program{static void Main(string[] args){Chicken chicken new Chicken();People zuDi new People();chicken.Crow zuDi.Action;chicken.StartCrow();Console.ReadLine();}}class Chicken{public event EventHandler Crow;public void StartCrow(){Console.WriteLine(koke-kokko);OnCrow(EventArgs.Empty);}protected virtual void OnCrow(EventArgs e){Crow?.Invoke(this,e);}}class People{public void Action(object sender, EventArgs e){Console.WriteLine(I get up.);Console.WriteLine(I started practicing my sword.);}}
}/*
Outputs:
koke-kokko
I get up.
I started practicing my sword.
*/这个例子是对闻鸡起舞的实现Chicken类是对发布者的实现包含使用event关键字声明的内置委托类型的事件以及触发事件。
建立事件和事件处理器之间的订阅关系。-取消事件和事件处理器之间的订阅关系。
为什么要取消事件和事件处理器之间的订阅关系防止内存泄漏。事件订阅者持有对事件发布者的引用当订阅者订阅了一个事件时事件发布者会将订阅者添加到其事件订阅者列表中。这意味着只要事件发布者存在订阅者对象就不会被垃圾回收器回收即使订阅者对象本身不再被其他部分的代码引用。
比如在这样一个软件中有很多可以点击的按钮每个按钮点击后会产生不同的效果。整个窗体就是一个发布者而每个按钮是一个订阅者如果我们每次点击完按钮产生相应效果后不取消按钮和窗体之间的订阅关系那么随着我们点击的按钮越来越多内存中需要存储越来越多这样的订阅关系而这种订阅关系并不会自动清除掉除非我们将软件关闭也就是发布者被干掉了。因此不及时取消订阅很容易导致内存泄漏。
Crow?.Invoke(this,e);着重说一下这行代码为什么要加?以及传入的参数this有什么用
2.1.1
这里的?是可空类型修饰符考虑一种情况将事件处理器订阅事件的这行代码注释掉然后将可空类型修饰符删掉这个时候再运行代码。 会报错“未将对象引用设置到对象的实例”我们知道Invoke方法是委托用来间接调用函数的C# 委托由于我们将chicken.Crow zuDi.Action;注释掉了也就是说委托是null没有引用到任何实例对象就会报错了。当没有任何的事件处理器订阅事件时处于代码的健壮性考虑我们会加上可空类型修饰符避免出现这种报错。通过使用?C#在调用Invoke方法之前会先检查Crow是否为空如果为空就不会去调用Invoke方法从而避免了空引用异常。
我们也可以显式地对Crow是否为空进行判断。
if(Crow ! null)
{Crow.Invoke(this, e);
}2.1.2 this
namespace ConsoleApp1
{class Program{static void Main(string[] args){Chicken chicken new Chicken();chicken.Name BigChicken;People zuDi new People();chicken.Crow zuDi.Action;chicken.StartCrow();Console.ReadLine();}}class Chicken{public event EventHandler Crow;public string Name { get; set; }public void StartCrow(){Console.WriteLine(koke-kokko);OnCrow(EventArgs.Empty);}protected virtual void OnCrow(EventArgs e){Crow?.Invoke(this, e);}}class People{public void Action(object sender, EventArgs e){if(sender ! null){Chicken chicken sender as Chicken;Console.WriteLine(I hear the {0} is crowing.,chicken.Name);Console.WriteLine(I get up.);Console.WriteLine(I started practicing my sword.);}}}
}/*
Outputs:
koke-kokko
I hear the BigChicken is crowing.
I get up.
I started practicing my sword.
*/this代表的是Chicken的实例。假设这样一种情况祖逖现在只有在听到BigChicken这一只鸡叫才会起床舞剑也就是说任何其他的鸡叫祖逖都不会做出反应那么这个时候就需要能在事件处理器中判断是哪一个对象触发的事件。sender是对this的引用。
2.1.3 使用匿名函数和lamda表达式
2.1.3.1 匿名函数
为了使程序更简洁我们可以使用匿名函数或lamda表达式来完成订阅者的功能。
namespace ConsoleApp1
{class Program{static void Main(string[] args){Chicken chicken new Chicken();chicken.Crow delegate (object sender, EventArgs e){Console.WriteLine(I get up.);Console.WriteLine(I started practicing my sword.);};chicken.StartCrow();Console.ReadLine();}}class Chicken{public event EventHandler Crow;public void StartCrow(){Console.WriteLine(koke-kokko);OnCrow(EventArgs.Empty);}protected virtual void OnCrow(EventArgs e){Crow?.Invoke(this, e);}}
}2.1.3.2 lamda表达式
namespace ConsoleApp1
{class Program{static void Main(string[] args){Chicken chicken new Chicken();chicken.Crow (sneder, e) {Console.WriteLine(I get up.);Console.WriteLine(I started practicing my sword.);};chicken.StartCrow();Console.ReadLine();}}class Chicken{public event EventHandler Crow;public void StartCrow(){Console.WriteLine(koke-kokko);OnCrow(EventArgs.Empty);}protected virtual void OnCrow(EventArgs e){Crow?.Invoke(this, e);}}
}2.1.4 异常处理
namespace ConsoleApp1
{class Program{static void Main(string[] args){Bell bell new Bell();Teather teather new Teather();Student student new Student();bell.Ring teather.Action;bell.Ring student.Action;bell.BellRing();Console.ReadLine();}}class Bell{public event EventHandler Ring;public void BellRing(){Console.WriteLine(Ding!Dang!);OnRing(EventArgs.Empty);}protected virtual void OnRing(EventArgs e){Ring?.Invoke(this,e);}}class Teather{public void Action(object sender, EventArgs e){Console.WriteLine(I walked into the classroom.);}}class Student{public void Action(object sender, EventArgs e){Console.WriteLine(I settled into my seat.);}}
}/*Outputs
Ding!Dang!
I walked into the classroom.
I settled into my seat.
*/上课铃声响起老师走进教室学生在座位上坐好。这个例子中一个事件的发布者对应着两个订阅者。如果老师这个订阅者发生异常我们来看看程序的运行结果。 程序直接抛出异常这就意味着不仅老师的事件处理函数无法执行学生的事件处理函数也无法执行。 当多个订阅者订阅发布者的消息时一个订阅者出现错误会导致后面所有的订阅者都无法接收到消息因此为了避免这种情况的发生需要进行异常处理。
namespace ConsoleApp1
{class Program{static void Main(string[] args){Bell bell new Bell();Teather teather new Teather();Student student new Student();bell.Ring teather.Action;bell.Ring student.Action;bell.BellRing();Console.ReadLine();}}class Bell{public event EventHandler Ring;public void BellRing(){Console.WriteLine(Ding!Dang!);OnRing(EventArgs.Empty);}protected virtual void OnRing(EventArgs e){Ring?.Invoke(this, e);}}class Teather{public void Action(object sender, EventArgs e){try{//Console.WriteLine(I walked into the classroom.);throw new Exception(Teacher throw an exception.);}catch (Exception ex){Console.WriteLine(Exception caught: {0}, ex.Message);}}}class Student{public void Action(object sender, EventArgs e){try{Console.WriteLine(I settled into my seat.);}catch (Exception ex){Console.WriteLine(Exception caught: {0}, ex.Message);}}}
}/*Outputs
Ding!Dang!
Exception caught: Teacher throw an exception.
I settled into my seat.
*/这样哪怕老师的事件处理函数抛出异常也不会影响学生的事件处理函数正常执行。
2.2 EventHandlerTEventArgs
namespace ConsoleApp1
{class Program{static void Main(string[] args){Phone phone new Phone();People people new People();phone.MessageReceived people.Action;phone.ReceiveMessage();Console.ReadLine();}}class MessageReceivedEventArgs:EventArgs{public string Message { get; }public MessageReceivedEventArgs(string message){Message message;}}class Phone{public event EventHandlerMessageReceivedEventArgs MessageReceived;public void ReceiveMessage(){Console.WriteLine(Ding,Ding,Ding.);MessageReceivedEventArgs message new MessageReceivedEventArgs(Hello,World!);OnMessageReceived(message);}protected virtual void OnMessageReceived(MessageReceivedEventArgs e){MessageReceived?.Invoke(this, e);}}class People{public void Action(object sender, MessageReceivedEventArgs e){string message e.Message;Console.WriteLine(I receive the mesage.);Console.WriteLine(The message is: {0}, message);}}
}/*Outputs:
Ding,Ding,Ding.
I receive the mesage.
The message is: Hello,World!
*/3、使用自定义委托类型声明事件
3.1 事件的完整声明
namespace ConsoleApp1
{public delegate void DingEventHandler(Phone phone, DingEventArgs e);public class DingEventArgs:EventArgs{public string Message { get; set; }}class Program{static void Main(string[] args){Phone phone new Phone();People people new People();phone.Ding new DingEventHandler(people.Action);DingEventArgs e new DingEventArgs();e.Message Hello,World!;phone.MessageReceived(e);Console.ReadLine();}}public class Phone{private DingEventHandler dingEventHandler;public string ID { get; }public Phone(){ID 123456;}public event DingEventHandler Ding{add{this.dingEventHandler value;}remove{this.dingEventHandler - value;}}public void MessageReceived(DingEventArgs e){Console.WriteLine(Ding,Ding!);Console.WriteLine(Here comes the message.);OnDing(e);}protected virtual void OnDing(DingEventArgs e){this.dingEventHandler?.Invoke(this, e);}}public class People{public void Action(Phone phone, DingEventArgs e){string id phone.ID;string message e.Message;Console.WriteLine(I receive message: [{0}], from {1},message,id);}}
}/*Outputs:
Ding,Ding!
Here comes the message.
I receive message: [Hello,World!], from 123456
*/以上是事件声明的完整形式它能够让我们理解事件运行的本质原理。都说事件是基于委托的那委托究竟在事件中是怎么发挥作用的呢 我们知道委托相当于C中的函数指针也就是对函数的引用我们可以通过调用委托实例的方法来间接调用委托所引用的函数而不是直接去调用函数这样做的好处是可以将函数封装在委托中把函数以参数的形式进行传递也可以将函数赋值给一个变量。参见C# 委托。
public delegate void DingEventHandler(Phone phone, DingEventArgs e);这行代码定义了一个委托类型。
private DingEventHandler dingEventHandler;这行代码定义了一个委托类型的私有字段。
public event DingEventHandler Ding
{add{this.dingEventHandler value;}remove{this.dingEventHandler - value;}
}这段代码是对事件的定义使用event关键字定义一个委托类型的事件。通过add和remove访问器来将委托和函数绑定在一起以及取消绑定value是上下文关键字代表传进来的函数方法。
// this.dingEventHandler value;
this.dingEventHandler new DingEventHandler(value);其实下面的代码才是完整的表达方式这一步的过程是将value所代表的函数封装到DingEventHandler委托中。
this.dingEventHandler?.Invoke(this, e);这一行代码是通过委托实例的Invoke方法来间接调用它所引用的函数。
phone.Ding new DingEventHandler(people.Action);这一行代码看似是将函数和事件建立联系实际上是将函数封装到委托中。 还有一点值得一提委托的签名必须和函数的签名保持一致。
3.2 事件的简化声明
namespace ConsoleApp1
{public delegate void DingEventHandler(Phone phone, DingEventArgs e);public class DingEventArgs:EventArgs{public string Message { get; set; }}class Program{static void Main(string[] args){Phone phone new Phone();People people new People();phone.Ding people.Action;DingEventArgs e new DingEventArgs();e.Message Hello,World!;phone.MessageReceived(e);Console.ReadLine();}}public class Phone{public string ID { get; }public Phone(){ID 123456;}public event DingEventHandler Ding;public void MessageReceived(DingEventArgs e){Console.WriteLine(Ding,Ding!);Console.WriteLine(Here comes the message.);OnDing(e);}protected virtual void OnDing(DingEventArgs e){Ding?.Invoke(this, e);}}public class People{public void Action(Phone phone, DingEventArgs e){string id phone.ID;string message e.Message;Console.WriteLine(I receive message: [{0}], from {1},message,id);}}
}事件的简化声明省略了委托字段的显式定义以及add和remove访问器。实际上尽管我们没有显式地将这些代码写出来但是编译器在背后仍然会生成这些代码。 我们使用C#的反编译器看看具体情况。 将程序丢进去。 可见反编译的结果中包含了私有的委托字段以及add和remove访问器。 所以事件的简化声明是C#为我们提供的语法糖它让我们能够以更简单的方式、更少的代码来声明事件。但是如果我们不了解事件的完整声明的话我们很容易误以为事件就是一个委托类型的字段实际上并不是。
4、委托和事件的区别
委托和事件的关系类似于字段和属性的关系。C# 类二——成员字段、属性、方法、事件。
namespace ConsoleApp1
{class Program{static void Main(string[] args){Student stu new Student();stu.Age 15;Console.WriteLine(stu.Age);Console.ReadLine();}}class Student{private int age;public int Age { get; set; }}
}我们知道出于数据安全的考虑字段的访问级别我们一般设置为private也就是只可以在类内访问而不能在类外访问在类外通过访问属性来间接访问字段。属性将字段进行封装保护了数据安全。实际上如果我们把字段的访问级别改为public然后删掉属性并不改变代码的运行结果但是会给代码带来潜在的风险。
class Student
{public int age;
}同样地事件也是对委托的一种封装事件的引入不允许在类外直接访问委托类型的字段private而只能通过事件public来间接访问委托。 namespace ConsoleApp1
{class Program{static void Main(string[] args){Bell bell new Bell();Teather teather new Teather();Student student new Student();bell.Ring teather.Action;bell.Ring student.Action;bell.BellRing();Console.ReadLine();}}class Bell{public EventHandler Ring;public void BellRing(){Console.WriteLine(Ding!Dang!);OnRing(EventArgs.Empty);}protected virtual void OnRing(EventArgs e){Ring?.Invoke(this, e);}}class Teather{public void Action(object sender, EventArgs e){try{throw new Exception(Teacher throw an exception.);}catch (Exception ex){Console.WriteLine(Exception caught: {0}, ex.Message);}}}class Student{public void Action(object sender, EventArgs e){try{Console.WriteLine(I settled into my seat.);}catch (Exception ex){Console.WriteLine(Exception caught: {0}, ex.Message);}}}
}将代码中的event关键字删掉代码功能由原来的委托类型事件的声明变成了委托类型字段的声明。再次运行代码发现运行结果并不会改变。这样的改动将委托直接暴露在了类外给程序带来了潜在的风险。 比如我们可以在类外直接调用委托的Invoke方法这样的结果是铃声还没有响老师就走进教室了学生也需要在位置上坐好这就不符合代码逻辑了。 而如果我们使用的是事件的话在类外根本就不允许有这种操作这个时候我们想要触发事件只能通过调用bell.BellRing();方法而如果我们想直接通过事件来调用Invoke方法编译器会报错事件Bell.Ring只能出现在或-的左边编译器压根就不允许我们有这种操作。
5、事件的作用
事件的封装性使得事件的触发逻辑和订阅者列表被封装在类内部外部代码不能直接调用事件也不能直接访问事件的订阅者列表从而提高了代码的安全性和稳定性。
松耦合程序设计的一大原则是“高内聚低耦合”事件让发布者和订阅者之间以一种松耦合的方式联系起来发布者不需要知道订阅者是谁订阅者也不需要知道发布者是谁只要订阅者的事件处理函数签名满足发布者中委托类型字段的签名就可以形成订阅关系。灵活性以动态调用和可扩展的方式对一件事情做出响应。动态调用指的是当感兴趣的事情发生时事件被触发发布者通知订阅者采取行动而如果感兴趣的事情没有发生那么发布者和订阅者都保持沉默状态。可扩展指的是我们如果想要增加订阅者列表无需对发布者代码进行任何修改只需要确保事件处理函数签名正确即可。发布者-订阅者模式。