在C#语言中,可以使用事件,让一个类或对象在发生某些事情时,通知(或者通俗易懂地讲,“告知”)其它类或对象——此时有某件事情发生了。
例如,1. 在闹钟响起时-->小明听到了闹钟的声音-->小明去通知/喊室友去上早8
- 在电话想起时-->妈妈听到了电话铃声-->妈妈去接电话
- 在敲门声响起时-->爸爸听到了敲门声-->爸爸去开门
上述例子中,事件就是指此刻发生的事情:闹钟响了、电话响了、门被敲响了等等。闹钟,电话以及门三种物品本身可以视为事件的发布者。因为,这三件物品通过自身主动或被动地发出声音,提醒着某人:此处发生了某事。
例子1中的小明,例子2中的妈妈和例子3中的爸爸,则可以视为事件的订阅者。因为一旦听到了物品发出了声音,他们就要做出对应的反应行为。
综上,可以把事件简单理解为:一发生xx事,xx人/xx事物就要做出xx反应。此处所列举的例子,均是指单一的个体(人)对事件做出了反应。实际上,可以有多人对此做出反应。比如,只设一个闹钟,在闹钟响了的时候,全寝的人都起床去上早8。如果是混寝,则可能只有计算机专业的2人去上课,其他二人听到之后,也会继续做自己目前所做的事情(继续睡或吃早饭)。以此例强调,事件发生时,只与订阅了此事件的对象有关,其他无关人员(对象)不受影响,无需做出反应。
而事件发生时,往往伴随着信息的传递。例如,闹钟响起的时候,代表“我”要去上课了。例如,去上高数课,此处传递的信息为:要去上的课的名称。
再例如,电话声响起的时候,手机/电话机上将显示当前的时间,与呼叫人的电话号码。此处传递的信息为:当前的时间与电话号码。
如果感觉这两段话难以理解,以正向思维来思考的话,我们在现实中描述这一事件的时候,通常是说:“xx人在x点钟打来了电话”。
又例如,门被敲响时,时钟上显示了时间,去开门的人通过猫眼看到了敲门者。虽然门本身不带时间,敲门者也不是门自己看出来的,但时间和敲门者是门被敲响这一事件发生时,所传递的两个关键信息。
以敲门为例,在 Visual Studio 中新建.Net 6项目,项目名称自定义,这里设为 ConsoleApp1。
首先,需要编写房门类 Door。此类中有两个属性:Color 属性用于描述门的颜色,Material 属性用于描述门的材质。这我们直接两个属性简单粗暴地定义为 string 类型,并分别将其默认值设为“棕色”,与“木质”;还需要在此类中定义 DoorKnocked 事件。Door 类暂时看起来如下:
internal class Door
{
public string Color { get; set; } = "Brown";
public string Material { get; set; } = "Wood";
public void Knock()
{
OnDoorKnocked(new());
}
/// <summary>
/// This event is the publisher, and can be raised when the door is knocked.
/// This is the event that subscribers can listen to.
/// </summary>
public event EventHandler DoorKnocked;
}
接下来需要定义敲门事件发生时,所要传递的信息。例如,如上所述的敲门者(string 类型的 KnockerOnTheDoor 属性,默认值为“女无名氏”)与敲门的时间(DateTime 类型的 KnockedAt 属性,默认值为系统当前的时间)。定义一个信息传递类 DoorKnockedEventArgs,并使之继承自 EventArgs 类:
internal class DoorKnockedEventArgs : EventArgs
{
public string KnockerOnTheDoor { get; set; } = "Jane Doe";
public DateTime KnockedAt { get; set; } = DateTime.Now;
}
此时,为了能够在事件每次触发时,都带着如上信息,需要对 Door 类中的事件进行小幅度修改,如下所示:
/// <summary>
/// This event is the publisher, and can be raised when the door is knocked.
/// This is the event that subscribers can listen to.
/// </summary>
public event EventHandler<DoorKnockedEventArgs> DoorKnocked;
我们还需要一个方法来触发事件,继续在 Door 类中添加以下代码:
protected virtual void OnDoorKnocked(DoorKnockedEventArgs e)
{
EventHandler<DoorKnockedEventArgs> handler = DoorKnocked;
handler?.Invoke(this, e);
}
这段代码可以保证:只有订阅了该事件的对象,才能将其触发。
其中?.Invoke(this, e)等价于
if (handler != null)
{
handler.Invoke(this,e);
}
然后,我们还需要继续定义一个敲门 Knock() 方法,用于模拟敲门的行为:
public void Knock()
{
OnDoorKnocked(new());
}
修改后的完整 Door 类的代码如下所示:
internal class Door
{
public string Color { get; set; } = "Brown";
public string Material { get; set; } = "Wood";
public void Knock()
{
OnDoorKnocked(new());
}
/// <summary>
/// This event is the publisher, and can be raised when the door is knocked.
/// This is the event that subscribers can listen to.
/// </summary>
public event EventHandler<DoorKnockedEventArgs> DoorKnocked;
protected virtual void OnDoorKnocked(DoorKnockedEventArgs e)
{
EventHandler<DoorKnockedEventArgs> handler = DoorKnocked;
handler?.Invoke(this, e);
}
}
接下来实际测试并感受一下事件的用法:
在 Main 方法中创建一个新的 Door 类型对象
var myDoor = new Door();
若要订阅 myDoor 实例的DoorKnocked 事件,需声明事件的订阅者(此处为两个方法):
public static void Program_OnDoorKnocked(object sender, DoorKnockedEventArgs e)
{
Console.WriteLine($"Door knocked by {e.KnockerOnTheDoor} at {e.KnockedAt}.");
}
public static void Program_OnDoorKnocked_WithTraceOnTheDooor(object sender, DoorKnockedEventArgs e)
{
Console.WriteLine($"A traced was left on the door with {e.KnockerOnTheDoor}'s hand.");
}
接下来订阅事件:
// Subscribe to the DoorKnocked event.
myDoor.DoorKnocked += Program_OnDoorKnocked;
myDoor.DoorKnocked += Program_OnDoorKnocked_WithTraceOnTheDooor;
模拟敲门过程:当按下键盘上的“k”键时,触发敲门事件;按下其它键时,结束程序:
while (true)
{
Console.WriteLine("Press 'k' to knock on the door, or any other key to exit.");
var key = Console.ReadKey(true).Key;
if (key == ConsoleKey.K)
{
myDoor.Knock();
await Task.Delay(2000);
}
else
{
break;
}
}
在键盘上按下5次“k”键,程序输出结果如下所示:
Press 'k' to knock on the door, or any other key to exit.
Door knocked by Jane Doe at 05/27/2025 23:57:33.
A traced was left on the door with Jane Doe's hand.
Press 'k' to knock on the door, or any other key to exit.
Door knocked by Jane Doe at 05/27/2025 23:57:35.
A traced was left on the door with Jane Doe's hand.
Press 'k' to knock on the door, or any other key to exit.
Door knocked by Jane Doe at 05/27/2025 23:57:37.
A traced was left on the door with Jane Doe's hand.
Press 'k' to knock on the door, or any other key to exit.
Door knocked by Jane Doe at 05/27/2025 23:57:39.
A traced was left on the door with Jane Doe's hand.
Press 'k' to knock on the door, or any other key to exit.
Door knocked by Jane Doe at 05/27/2025 23:57:41.
A traced was left on the door with Jane Doe's hand.
Press 'k' to knock on the door, or any other key to exit.
|
完整程序代码如下所示:
namespace ConsoleApp1;
#nullable disable
internal class Program
{
static async Task Main()
{
var myDoor = new Door();
// Subscribe to the DoorKnocked event.
myDoor.DoorKnocked += Program_OnDoorKnocked;
myDoor.DoorKnocked += Program_OnDoorKnocked_WithTraceOnTheDooor;
while (true)
{
Console.WriteLine("Press 'k' to knock on the door, or any other key to exit.");
var key = Console.ReadKey(true).Key;
if (key == ConsoleKey.K)
{
myDoor.Knock();
await Task.Delay(2000);
}
else
{
break;
}
}
}
public static void Program_OnDoorKnocked(object sender, DoorKnockedEventArgs e)
{
Console.WriteLine($"Door knocked by {e.KnockerOnTheDoor} at {e.KnockedAt}.");
}
public static void Program_OnDoorKnocked_WithTraceOnTheDooor(object sender, DoorKnockedEventArgs e)
{
Console.WriteLine($"A traced was left on the door with {e.KnockerOnTheDoor}'s hand.");
}
}
internal class Door
{
public string Color { get; set; } = "Brown";
public string Material { get; set; } = "Wood";
public void Knock()
{
OnDoorKnocked(new());
}
/// <summary>
/// This event is the publisher, and can be raised when the door is knocked.
/// This is the event that subscribers can listen to.
/// </summary>
public event EventHandler<DoorKnockedEventArgs> DoorKnocked;
protected virtual void OnDoorKnocked(DoorKnockedEventArgs e)
{
EventHandler<DoorKnockedEventArgs> handler = DoorKnocked;
handler?.Invoke(this, e);
}
}
internal class DoorKnockedEventArgs : EventArgs
{
public string KnockerOnTheDoor { get; set; } = "Jane Doe";
public DateTime KnockedAt { get; set; } = DateTime.Now;
}
总结:在此例中,定义了DoorKnocked 事件, DoorKnockedEventArgs 类用于承载事件发时所传递的信息。事件的2个订阅者分别名为 Program_OnDoorKnocked 与 Program_OnDoorKnocked_WithTraceOnTheDooor。当敲门事件发生时,Program_OnDoorKnocked 方法在控制台中打印敲门者与敲门时间;Program_OnDoorKnocked_WithTraceOnTheDooor 在控制台中打印了一条信息:门上留下了敲门者xxx的手印。
初学事件时,会感觉某些地方隐晦难懂,可结合本文开头时编写的几个案例,进行结合分析与思考学习。