C#中的事件(委托的发布和订阅、事件的发布和订阅、EventHandler类、Windows事件)
目录
一、委托的发布和订阅
1.订阅操作符号“+="和取消订阅操作符号“-=”
2.示例源码
二、事件的发布和订阅
三、EventHandler类
四、Windows事件
C#中的事件是指某个类的对象在运行过程中遇到的一些特定事情,而这些特定的事情有必要通知给这个对象的使用者。当发生与某个对象相关的事件时,类会使用事件将这一对象通知给用户,这种通知即称为“引发事件”。引发事件的对象称为事件的源或发送者。
一、委托的发布和订阅
由于委托能够引用方法,而且能够链接和删除其他委托对象,因而就能够通过委托来实现事件的“发布和订阅”。
通过委托来实现事件处理的过程,通常需要以下4个步骤:
• 定义委托类型,并在发布者类中定义一个该类型的公有成员。
• 在订阅者类中定义委托处理方法。
• 订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上。
• 发布者对象在特定的情况下“激发”委托操作,从而自动调用订阅者对象的委托处理方法。
1.订阅操作符号“+="和取消订阅操作符号“-=”
“+=”在这里不是逻辑运算符,而是用于指定响应事件时要调用的方法。这类方法称为事件处理程序,叫 注册/订阅事件,用在操作类名后。
//订阅符号+=
public static void SubscribeToRing(SchoolRing schoolRing)
{
schoolRing.OnBellSound += SchoolJow;
}
与之相反功能的“-=”就是取消订阅、退订操作符。
//取消订阅操作符“-=”
public static void CancelSubscribe(SchoolRing schoolRing)
{
schoolRing.OnBellSound -= SchoolJow;
}
2.示例源码
// 委托的发布和订阅事件
namespace Demo
{
class Program
{
static void Main(string[] args)
{
SchoolRing sr = new(); //创建学校铃声类的对象
Students.SubscribeToRing(sr); //订阅铃声
Console.Write("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
sr.Jow(Convert.ToInt32(Console.ReadLine())); //打铃动作
Console.ReadLine();
}
}
public delegate void RingEvent(int ringKind); //声明一个委托类型
/// <summary>
/// 定义铃声类SchoolRing
/// 类中发布一个委托,定义函数Jow
/// </summary>
public class SchoolRing
{
public RingEvent? OnBellSound; //委托发布,就好像定义一个实例对象
public void Jow(int ringKind) //定义一个公有成员Jow(),打铃
{
if (ringKind == 1 || ringKind == 2)
{
Console.Write(ringKind == 1 ? "上课铃声响了," : "下课铃声响了,");
if (OnBellSound != null) //不等于空,说明它已经订阅了具体的方法(即它已经引用了具体的方法)
{
OnBellSound!(ringKind); //回调OnBellSound委托所订阅(或引用)的具体方法
}
}
else
{
Console.WriteLine("这个铃声参数不正确!");
}
}
}
/// <summary>
/// 定义学生类Students
/// 类中定义三个函数
/// </summary>
public class Students
{
public static void SubscribeToRing(SchoolRing schoolRing) //学生们订阅铃声这个委托事件
{
schoolRing.OnBellSound += SchoolJow;
}
public static void SchoolJow(int ringKind)
{
if (ringKind == 2) //打了下课铃
{
Console.WriteLine("同学们开始课间休息!");
}
else if (ringKind == 1) //打了上课铃
{
Console.WriteLine("同学们开始认真学习!");
}
}
public static void CancelSubscribe(SchoolRing schoolRing) //取消订阅铃声动作
{
schoolRing.OnBellSound -= SchoolJow;
}
}
}
二、事件的发布和订阅
使用事件的目的是:解决安全隐患和不能干涉其他订阅者。事件的使用方法:C#提供了专门的事件处理机制,以保证事件订阅的可靠性,其做法是在发布委托的定义中加上event关键字,其他代码不变。
//事件的使用方法
public event RingEvent OnBellSound; //事件发布
//不安全的事件订阅,当不使用event关键字时,系统会会忽视威胁的存在
//当使用event关键字修饰时,系统会报错
schoolRing.OnBellSound = SchoolJow; //系统会报错的,应使用+=
schoolRing.OnBellSound = null; //系统会报错的,禁止指向null
schoolRing.OnBellSound2 = SchoolJow; //系统会报错的,事件只能由自身触发
三、EventHandler类
在事件发布和订阅的过程中,定义事件的类型(即委托类型)是一件重复性的工作,为此,.NET类库中定义了一个EventHandler委托类型,并建议尽量使用该类型作为事件的委托类型。该委托类型的定义为:
public delegate void EventHandler(object sender,EventArgs e);
其中,
object类型的参数sender表示引发事件的对象,由于事件成员只能由类型本身(即事件的发布者)触发,因此在触发时传递给该参数的值通常为this。例如,可将SchoolRing类的OnBellSound事件定义为EventHandler委托类型,那么触发该事件的代码就是“OnBellSound(this,null);”。
EventHandler委托的第二个参数e表示事件中包含的数据。如果发布者还要向订阅者传递额外的事件数据,那么就需要定义EventArgs类型的派生类。
// EventHandler类
namespace _09_1
{
class Program
{
/// <summary>
/// 操作流程:创建发布者实例→订阅该实例→发布者开始发布
/// </summary>
static void Main(string[] args)
{
SchoolRing sr = new(); //创建学校铃声类的对象
Students.SubscribeToRing(sr); //订阅铃声
Console.Write("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
sr.Jow(Convert.ToInt32(Console.ReadLine())); //发布者触发打铃动作,事件只能由发布者触发
Console.ReadLine();
}
}
public delegate void RingEvent(int ringKind); //声明一个委托类型
/// <summary>
/// 发布者
/// 校铃种类及对应的处理方法
/// 定义铃声类SchoolRing,类中发布一个委托,定义函数Jow方法
/// </summary>
public class SchoolRing
{
public event EventHandler? OnBellSound; //委托发布,就好像定义一个实例对象
public void Jow(int ringKind) //定义一个公有成员Jow(),打铃方法
{
if (ringKind == 1 || ringKind == 2)
{
Console.Write(ringKind == 1 ? "上课铃声响了," : "下课铃声响了,");
if (OnBellSound != null) //不等于空,说明它已经订阅了具体的方法(即它已经引用了具体的方法)
{ //为了安全,事件成员只能由类型本身触发(this),
OnBellSound!(this, new Students.RingEventArgs(ringKind));
}
}
else
{
Console.WriteLine("这个铃声参数不正确!");
}
}
}
/// <summary>
/// 订阅者
/// 定义学生类Students
/// 类中定义三个函数:订阅、订阅方法、取消订阅
/// </summary>
public class Students
{
/// <summary>
/// 订阅
/// </summary>
public static void SubscribeToRing(SchoolRing schoolRing) //学生们订阅铃声这个委托事件
{
schoolRing.OnBellSound += SchoolJow;
}
/// <summary>
/// EventHandler委托的第二个参数e表示事件中包含的数据。
/// </summary>
/// <param name="sender">
/// 事件的订阅者可以通过sender参数来了解是哪个对象触发的事件(这里当然是事件的发布者),
/// 不过在访问对象时通常要进行强制类型转换
/// </param>
/// <param name="e"></param>
public static void SchoolJow(object? sender, EventArgs e)
{
if (((RingEventArgs)e).RingKind == 2) //下课铃,e强制转化内RingEventArgs类型
{
Console.WriteLine("同学们开始课间休息!");
}
else if (((RingEventArgs)e).RingKind == 1) //上课铃,e强制转化内RingEventArgs类型
{
Console.WriteLine("同学们开始认真学习!");
}
}
/// <summary>
/// 取消订阅
/// </summary>
/// <param name="schoolRing"></param>
public static void CancelSubscribe(SchoolRing schoolRing)
{
schoolRing.OnBellSound -= SchoolJow;
}
/// <summary>
/// EventArgs类型的派生类
/// 如果发布者还要向订阅者传递额外的事件数据,那么就需要定义EventArgs类型的派生类。
/// 例如,由于需要把打铃参数(1或2)传入事件中,则可以定义如下的RingEventArgs类:
/// </summary>
/// <param name="ringKind">
/// 铃声参数
/// </param>
public class RingEventArgs(int ringKind) : EventArgs
{
//描述铃声种类的字段
private readonly int ringKind = ringKind;
//获取打铃参数
public int RingKind
{
get { return ringKind; }
}
}
}
}
四、Windows事件
事件在Windows这样的图形界面程序中有着极其广泛的应用,事件响应是程序与用户交互的基础。用户的绝大多数操作,都可以触发相关的控件事件。关于此类事件,详见作者发布的有关Windows窗体应用的文章,此处省略十万字。