事件(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; 无需自定义委托 |
相关笔记