C#笔记进阶篇07 事件
——本系列是基于人民邮电出版社《C#2008 C#图解教程》、清华大学出版社《C#入门经典(第五版)》两本书的自学C#笔记,如果您发现了本文的纰漏,还望不吝指正。
写在前边
事件的很多方面和委托相似。事件就好像被简化的针对特殊用途的委托。——人民邮电出版社《C#2008 C#图解教程》
*复习:类是一种能存储数据并执行代码的数据结构,它包含:
- 数据成员——存储数据
包括:字段、常量 - 函数成员——执行代码
包括:方法,属性,构造函数,索引,运算符,事件,析构函数
在之前基础篇的笔记中:
C#笔记10 类:基础
C#笔记11 类:类成员补充01
C#笔记12 类:类成员补充02
已经介绍了九种类成员类型中的七种(字段,方法,常量,属性,索引,构造函数和析构函数)
1. 事件的定义
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。——菜鸟教程
- 事件只负责告诉每个函数什么时候被调用,这些函数到底干了什么,事件并不关心
- C# 中使用事件机制实现线程间的通信
1.1 事件包含了一个私有的委托
- 事件提供了对它的私有控制委托的结构化访问
- 对于事件我们只可以添加、删除或调用事件处理程序
- 事件触发时,它调用委托来依次调用调用列表中的方法
1.2 事件中使用的代码
- 委托类型声明:事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型声明进行描述。
- 事件处理程序声明:这些在订阅者类的方法(事件处理程序)中的描述会在事件触发时被执行。它们不需要有独立的方法,它们可以是匿名方法或lambda表达式。
- 事件声明:这个事件发布者类中的声明保存并调用事件处理程序。
- 事件注册:这段代码把事件连接到事件处理程序。
- 触发事件的代码:发布者类中的这段代码调用事件导致它调用事件处理程序。
1.3 事件基本处理过程
订阅 -> 发布 -> 执行
- 首先,应用程序创建一个可以引发事件的对象。当满足条件时,这个对象会引发一个事件。
- 接着,应用程序订阅事件,定义一个方法,该方法可以与事件指定的委托类型一起使用,把这个方法的一个引用传送给事件
- 引发事件后,就通知订阅器,调用对象上的事件处理方法
2. 事件的声明
事件的本质是成员,必须声明在类或结构中
- 由于事件不是类型,我们不能使用对象创建表达式(new表达式)来创建它的对象
- 事件成员被隐式自动初始化为null
1).在类的内部声明事件,首先必须声明该事件的委托类型。例如:
public delegate void BoilerLogHandler(string status);
2).然后,声明事件本身,使用 event 关键字:
//基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
//声明为public,其他类和结构可以在上边注册事件处理程序
上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。
*可以通过使用逗号分隔的列表在一个声明语句中声明一个以上的事件。还可以使用static关键字让事件变成静态的
例:下面语句声明了三个事件
public event BoilerLogHandler MyEvent1, MyEvent2, OtherEvent;
例:下面语句声明静态的事件
public static event BoilerLogHandler Elapsed;
3. 触发事件
事件本身只是保存了需要被调用的事件处理程序,触发事件本身看起来就像调用函数一样
// 基于上面名为 BoilerEventLog 的事件
if(BoilerEventLog!=null) //如果有订阅类,才会引发事件
BoilerEventLog(<parameter list>); //参数列表
4. 订阅事件
类或者对象可以通过事件向其他类或者对象通知发生的相关事情。发送(或引起)事件的类称之为发布器(publisher),接收(或处理)事件的类称为订阅器(subscriber)
发布器(publisher)和 订阅器(subscriber)
-
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器类的对象调用这个事件,并通知其他的对象。
-
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器类中的委托调用订阅器类中的方法(事件处理程序)。
使用+=运算符来为事件 增加事件处理程序
使用-=运算符来为事件 移除事件处理程序
例:
using System;
namespace SimpleEvent
{
/***********发布器类***********/
public class EventTest
{
private int value;
public delegate void NumManipulationHandler();
public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
if ( ChangeNum != null )
{
ChangeNum(); /* 事件被触发 */
}
else
{
Console.WriteLine( "event not fire" );
Console.ReadKey(); /* 回车继续 */
}
}
public EventTest()
{
int n = 5;
SetValue( n );
}
public void SetValue( int n )
{
if ( value != n )
{
value = n;
OnNumChanged();
}
}
}
/***********订阅器类***********/
public class subscribEvent
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}
/***********触发***********/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
//源码来自 菜鸟教程 https://www.runoob.com/csharp/csharp-event.html
控制台输出结果:
event not fire
event fire
event fire
5. 标准事件EventHandler
EventHandler 标准的预定义委托类型
.NET BCL使用的并被指定为事件使用标准的预定义委托类型
public daelegate void EventHandler(object sender, EventArgs e);
- 第一个参数 object sender 用来保存触发事件的对象的引用。
- 第二个参数 EventArgs e 用来保存有关状态对于应用程序来说是否合适的状态信息
public event EventHandler <event-name>; //事件名
1). 通过扩展EventArgs来传递函数
EventArgs被设计为不能传递任何数据,如果需要传递数据,必须声明一个从EventArgs继承的类,使用合适的字段来保存需要传递的数据
例:下面的代码声明一个自定义类,它能将字符串存储在名称为Message的字段中
public class MyTCEvenArgs:EventArgs
{
public strig Message; //存储message
public MyTCEvenArgs(string s) //构造函数设置message
{
Message = s;
}
}
2).使用自定义委托来使用新的自定义类
- 使用非泛型委托
//使用上文自定义类 MyTCEvenArgs:EventArgs
public delegate void MyEventHandle (object sender, MyTCEvenArgs e);
- 使用EventHandler泛型委托
//使用上文自定义类 MyTCEvenArgs:EventArgs
public event EventHandler<MyTCEvenArgs> <event-name>;
例:下面的代码中,事件DoWork 注册了匿名函数 ,功能是 输出显示参数值Message
调用Begin(“1”)函数,显示 1; 此时通过e.Message就可得到传入的参数。
public class MyTCEvenArgs:EventArgs
{
public strig Message; //存储message
public MyTCEvenArgs(string s) //构造函数设置message
{
Message = s;
}
}
public class DoTest
{
public DoTest() { }
//定义事件
public event EventHandler<MyEventArgs> DoWork; //使用EventHandler泛型委托 DoWork
public void Begin(string val)
{
if (DoWork != null)
{
MyEventArgs e = new MyEventArgs(val);
DoWork(this, e);
}
}
}