CodeGym /课程 /C# SELF /发布-订阅 模板 和 C# 里的 事件 ( event

发布-订阅 模板 和 C# 里的 事件 ( event)

C# SELF
第 52 级 , 课程 0
可用

1. 介绍

在写程序时,你不可避免会遇到一种场景:某一部分需要“通知”其他部分发生了重要事情。经典例子 — 用户点击了鼠标,这个事件需要处理。在日常生活中我们也处处在事件里:水壶在厨房里响了——你听到信号就赶去关火。咖啡洒到键盘上——心一紧——你立刻去抢救笔记本。编程遵循同样的规律。

事件 是一种机制,允许源对象(发布者)通知其他对象(订阅者)发生的变化或动作。可以理解为“我广播了——谁在听谁就响应”。

在 C# 里事件是一种基于 delegate 类型的特殊结构。delegate 定义了回调的签名——订阅者会如何被调用。事件用关键字 event 声明,delegate 用关键字 delegate 声明。

为什么需要事件?

  • 松耦合:发布者不知道订阅者的存在——只发出信号。
  • 架构灵活:可以动态添加和移除处理器,而不用改发布者代码。
  • 可扩展:加了新订阅者——它就马上开始接收通知。

经典的“发布-订阅”模板

假设有一个 “火警报警器” 类(发布者)和一个 “楼里的人” 类(订阅者)。报警器一响,给所有人发信号——不管楼里有多少人、他们在哪儿。这就是发布-订阅(或 Observer)模式。

发布者不知道有多少或哪些订阅者——它仅仅发出通知,其他对象自己决定订阅或忽略。

在 C# 里怎么运作?

  • 发布者:定义事件 (event),提供订阅/取消订阅。
  • 订阅者:订阅事件并实现处理器(当事件发生时处理器会被调用)。

2. 事件实战:第一个例子

从房子到代码,描述一个最简单的模型。假设有一个控制台应用,里面的计时器对象每秒“滴答”,不同的处理器会响应(比如在控制台输出“滴答”或统计滴答次数)。

第 1 步。定义 delegate 和 事件


public class SimpleTimer
{
    // 为事件声明 delegate
    public delegate void TickEventHandler(object sender, EventArgs e);

    // 基于 delegate 的事件
    public event TickEventHandler Tick;

    public void Start(int count)
    {
        for (int i = 0; i < count; i++)
        {
            System.Threading.Thread.Sleep(1000); // 模拟一次滴答!
            OnTick(); // 触发事件!
        }
    }

    protected virtual void OnTick()
    {
        // 如果有订阅者则调用事件 (Tick != null)
        Tick?.Invoke(this, EventArgs.Empty);
    }
}

这里发生了什么?

  • 定义了 delegate TickEventHandler,采用常规签名 object sender, EventArgs e
  • 事件 Tick 是处理器订阅点。
  • 方法 Start 模拟“滴答”,按计时调用 OnTick
  • OnTick 事件被安全调用:Tick?.Invoke(..., EventArgs.Empty)。

第 2 步。订阅事件


class Program
{
    static void Main()
    {
        var timer = new SimpleTimer();

        // 订阅 Tick 事件
        timer.Tick += Timer_Tick;

        timer.Start(3);

        // 如果需要,可以取消订阅
        timer.Tick -= Timer_Tick;
    }

    static void Timer_Tick(object sender, EventArgs e)
    {
        Console.WriteLine("滴答!");
    }
}

我们创建计时器,用操作符 += 订阅,然后每次滴答都会调用处理器。取消订阅用操作符 -=

3. 有用的细节

为什么事件比“硬编码”调用更好?

如果 SimpleTimerOnTick 里直接写控制台,类会和具体动作强耦合。事件解放了代码:计时器不知道订阅者会做什么——发射火箭、写日志到文件或发邮件都行。

事件和 delegate 的重要区别

  • delegate 是对方法的“指针”,而 事件 是带访问限制的 delegate。
  • 订阅者只能订阅/取消订阅;外部不能直接触发事件——只有发布者可以。
  • 要声明事件,在 delegate 类型前加上修饰符 event ——编译器会保证正确的访问模型。

C# 事件工作流程的简短示意


+------------------+          +------------------------------+
|                  |          |                              |
|     发布者       | <------> |         订阅者               |
| (Publisher/Event)|          |      (Subscriber/Handler)    |
|                  |          |                              |
+------------------+          +------------------------------+
         | 1) 声明事件             | 2) 订阅该事件
         | 3) 触发它               | 4) 实现处理器

什么时候用事件?

  • 需要通知不确定数量的监听者时。
  • 不想把动作逻辑绑定在源类内部时。
  • UI、异步交互、系统通知——这些地方都离不开事件。

C# 事件实现的简短要点

  • 事件不能被“外部”触发:只有发布者代码可以调用 Invoke
  • 订阅/取消订阅:操作符 +=/-=;可以有多个处理器。
  • 事件本质上是 delegate 列表:事件发生时会按订阅顺序调用所有处理器。
  • 推荐的 delegate:使用 EventHandlerEventHandler<TEventArgs> 与 .NET 生态兼容。

4. 新手常见错误

忘记检查是否有订阅者:Tick != null。最好用安全调用:Tick?.Invoke(...)。

订阅了事件但在处理器不再需要时没取消订阅。这可能会让对象被保留在内存中,导致内存泄漏。

尝试从外部类“调用”事件——编译器不会允许。不能写类似 game.GameOver(),如果它是事件而不是方法的话。

不遵守 delegate 的签名。对于事件使用标准的 EventHandlerEventHandler<TEventArgs> ——这样代码才和其他 .NET 库兼容。

2
任务
C# SELF, 第 52 级, 课程 0
已锁定
创建事件和订阅者
创建事件和订阅者
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION