csharp:事件

事件 (Events)

事件 (Event) 是 C# 中非常核心但也相对难以理解的概念。它是类与类之间通信的重要机制。

什么是事件?

指某个类的对象在运行过程中,遇到了一些特定的事情

  • 这些特定的事情需要通知到对象的使用者(订阅者)。
  • 当发生与某个事件相关的动作时,类就会使用事件将这个对象通知给用户。
  • 这种通知的操作称为 引发事件 (Raising an Event)

为什么说事件很难理解? 很多初学者容易混淆“委托”和“事件”。 简单来说:事件是基于委托的。如果把委托看作是“函数指针的容器”,那么事件就是对这个容器的一种“封装”和“保护”。

我们可以用生活中的例子来类比:

  • 发布者 (Publisher):烧水壶。
  • 订阅者 (Subscriber):你。
  • 事件:水开了(特定的事情)。
  • 处理方法:当水开了,水壶(发布者)发出鸣叫(通知),你(订阅者)听到后去关火(处理方法)。

在学习 `event` 关键字之前,我们先看看如何仅使用 委托 (Delegate) 来实现发布和订阅模式。这有助于理解事件的底层原理。

根据教程定义,通过委托实现事件处理的过程,通常需要以下 4 个步骤:

  1. (1) 定义委托类型,并在发布者类中定义一个该类型的公有成员。
  2. (2) 在订阅者类中定义委托处理方法。
  3. (3) 订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上。
  4. (4) 发布者对象在特定的情况下“激发”委托操作,从而自动调用订阅者对象的委托处理方法。
using System;
 
namespace DelegateDemo
{
    // 1. 定义委托类型
    public delegate void NotifyEventHandler();
 
    // 发布者类 (Publisher)
    public class Heater
    {
        // 1. 在发布者类中定义一个该类型的公有成员 (注意:这里是 public 字段)
        public NotifyEventHandler OnBoil; 
 
        public void BoilWater()
        {
            Console.WriteLine("水壶:开始烧水...");
            // 模拟烧水过程
            System.Threading.Thread.Sleep(2000);
 
            // 4. 发布者在特定情况下“激发”委托
            if (OnBoil != null)
            {
                Console.WriteLine("水壶:水开了!(激发委托)");
                OnBoil(); // 调用委托列表中的所有方法
            }
        }
    }
 
    // 订阅者类 (Subscriber)
    public class Alarm
    {
        // 2. 在订阅者类中定义委托处理方法
        public void MakeAlert()
        {
            Console.WriteLine("报警器:滴滴滴!水开了!");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Heater heater = new Heater();
            Alarm alarm = new Alarm();
 
            // 3. 订阅者将处理方法链接到发布者的委托成员上
            heater.OnBoil += alarm.MakeAlert;
 
            // 开始运行
            heater.BoilWater();
        }
    }
}

纯委托模式的缺陷: 在上面的代码中,`OnBoil` 是一个 `public` 字段。这意味着外部类不仅可以订阅 (`+=`),还可以:

  • 直接赋值 (`heater.OnBoil = null;`) —— 这会清空所有其他人的订阅!
  • 直接调用 (`heater.OnBoil();`) —— 即使水没开,外部也能伪造事件!

这破坏了封装性,因此我们需要 事件 (Event)

为了解决纯委托暴露出的安全问题,C# 引入了 `event` 关键字。 事件本质上是对委托的封装。它限制了外部类对委托的操作权限。

事件的特性:

  • 在定义事件的类内部:可以像普通委托一样调用(引发事件)。
  • 在定义事件的类外部:只能使用 `+=` (订阅) 和 `-=` (取消订阅),不能使用 `=` (赋值) 或直接调用。

我们将代码稍作修改,只需加上 `event` 关键字。

using System;
 
namespace EventDemo
{
    // 定义委托类型
    public delegate void NotifyEventHandler();
 
    public class Heater
    {
        // 【关键修改】这里加上了 event 关键字
        // 虽然它还是 public,但编译器会对它进行保护
        public event NotifyEventHandler OnBoil; 
 
        public void BoilWater()
        {
            Console.WriteLine("水壶:开始烧水...");
 
            // 只有在类的内容,才能调用这个事件
            if (OnBoil != null)
            {
                OnBoil(); 
            }
        }
    }
 
    public class Alarm
    {
        public void MakeAlert()
        {
            Console.WriteLine("报警器:滴滴滴!");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Heater heater = new Heater();
            Alarm alarm = new Alarm();
 
            // 订阅事件 (合法)
            heater.OnBoil += alarm.MakeAlert;
 
            // heater.OnBoil = null; // 【报错】无法在外部直接赋值
            // heater.OnBoil();      // 【报错】无法在外部直接调用
 
            heater.BoilWater();
        }
    }
}
特性 公有委托字段 (Delegate) 事件 (Event)
定义方式 `public DelegateType Name;` `public event DelegateType Name;`
外部订阅 支持 `+=` 和 `-=` 支持 `+=` 和 `-=`
外部赋值 支持 `=` (危险,会覆盖他人) 禁止 (编译错误)
外部调用 支持 `Name()` (危险,可伪造) 禁止 (编译错误)
安全性 高 (封装性好)

使用 `event` 关键字,就是为了保证“发布者负责触发,订阅者负责处理,除此之外谁也不能乱动”的契约。

请输入您的评论. 可以使用维基语法:
 

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • csharp/事件.txt
  • 最后更改: 2025/11/27 12:45
  • 张叶安