跳转至

事件(Event)#

事件是委托的进一步封装,用于实现发布-订阅模式。外部只能订阅(+=)或取消(-=)事件,不能直接触发。

事件三要素#

角色 说明
发布者(Publisher) 声明事件、定义触发机制
订阅者(Subscriber) 订阅事件、提供处理方法
事件(Event) 连接发布者和订阅者的桥梁

基本示例#

// ── 发布者 ──
class PublisherDemo
{
    // 1. 声明委托 — 定义"能做什么"
    public delegate void MyDelegate(string v);

    // 2. 声明事件 — 对外只有订阅/取消
    public event MyDelegate MyEven;

    // 3. 触发事件 — 只能在发布者内部触发
    public void SetValue(string v)
    {
        MyEven(v);
    }
}

// ── 订阅者 ──
class SubscribeDemo
{
    public void Print(string v)
    {
        Console.WriteLine(v);
    }
}

// ── 使用 ──
PublisherDemo publisher = new PublisherDemo();
SubscribeDemo subscribe = new SubscribeDemo();

publisher.MyEven += subscribe.Print;   // 订阅事件
publisher.SetValue("罗");              // 触发事件 → "罗"

// publisher.MyEven("罗");  // ❌ 编译错误:事件只能在发布者内部触发

事件 vs 委托的区别#

对比 委托 事件
外部触发 ✅ 可以直接调用 ❌ 只能在声明类内部触发
外部操作 可以 = 赋值(覆盖) 只能 += 订阅 / -= 取消
设计意图 通用回调 发布-订阅模式

事件触发规则#

事件触发流程:
  publisher.SetValue("罗")
    └─ MyEven(v)            ← 只能在 PublisherDemo 内部触发
         ├─ subscribe.Print("罗")  → 输出 "罗"
         └─ ...(其他订阅者的方法)

练习一:水温监控系统#

// 发布者:热水器
class WaterStaus
{
    public delegate void Staus(int a);
    public event Staus Water;

    public void Hot()
    {
        for (int i = 0; i <= 100; i++)
        {
            Water(i);                // 触发事件
            Thread.Sleep(200);       // 每 200ms 升温 1 度
        }
    }
}

// 订阅者 1:温度显示器
class Monitor
{
    public void Show(int a)
    {
        Console.WriteLine("当前水温:" + a + " ℃");
    }
}

// 订阅者 2:报警器
class Alarm
{
    public void Ding(int a)
    {
        if (a == 100)
        {
            Console.WriteLine("水烧开了");
            Console.Beep(800, 5000);
        }
    }
}

// 使用
WaterStaus water = new WaterStaus();
Monitor monitor = new Monitor();
Alarm alarm = new Alarm();

water.Water += monitor.Show;    // 订阅:显示器
water.Water += alarm.Ding;      // 订阅:报警器
water.Hot();                    // 开始加热

练习二:锅炉监控系统#

class Boilder
{
    public event Action<double> THigh;   // 温度过高事件
    public event Action<double> TLow;    // 温度过低事件
    public event Action<double> PHigh;   // 压力过高事件
    public event Action<double> PLow;    // 压力过低事件
    public event Action<double, double> OnLog;  // 日志事件

    double temperature;
    public double Temperature
    {
        get { return temperature; }
        set
        {
            temperature = value;
            if (value > 300) THigh?.Invoke(value);
            if (value < 200) TLow?.Invoke(value);
        }
    }

    private double pressure;
    public double Pressure
    {
        get { return pressure; }
        set
        {
            pressure = value;
            if (value > 2000) PHigh?.Invoke(value);
            if (value < 500) PLow?.Invoke(value);
        }
    }

    public void OnLog0()
    {
        OnLog?.Invoke(temperature, pressure);
    }
}

// 订阅者:温度监控器
class TemperMonitor
{
    public void High(double t) { Console.WriteLine($"温度过高,现在温度:{t} ℃"); }
    public void Low(double t)  { Console.WriteLine($"温度过低,现在温度:{t} ℃"); }
}

// 订阅者:压力监控器
class PressMonitor
{
    public void High(double p) { Console.WriteLine($"压力过高,现在压力:{p} P"); }
    public void Low(double p)  { Console.WriteLine($"压力过低,现在压力:{p} P"); }
}

// 使用
Boilder boilder = new Boilder();
TemperMonitor te = new TemperMonitor();
PressMonitor pr = new PressMonitor();

boilder.THigh += te.High;
boilder.TLow  += te.Low;
boilder.PHigh += pr.High;
boilder.PLow  += pr.Low;

// Lambda 订阅日志事件
boilder.OnLog += (a, b) =>
{
    Console.WriteLine($"{DateTime.Now:MMMdd日HH时mm秒}测试完毕,现在温度{a},压力{b}");
};

Random r = new Random();
for (int i = 0; i < 100; i++)
{
    boilder.Temperature = r.Next(888);
    boilder.Pressure = r.Next(100, 3333);
    boilder.OnLog0();
}

关键知识点#

概念 说明
发布者 声明 event 委托字段,内部触发
订阅者 提供处理方法,+= 订阅
触发限制 事件只能在声明类内部触发(外部调用会编译错误)
空值保护 触发前使用 事件?.Invoke() 判断是否有订阅者
Action 事件 public event Action<double> THigh; 无需自定义委托

相关笔记#