C#观察者模式:事件驱动编程的三种实现方式
1. 观察者模式像订阅杂志一样理解事件驱动第一次听说观察者模式时我的反应和大多数新手一样困惑——这到底是个什么魔法直到某天在咖啡厅看到一位顾客订阅杂志的过程突然茅塞顿开。想象你走进报刊亭对老板说有新刊到货时请通知我。这就是观察者模式的精髓你观察者不需要每天来店里查看轮询而是在感兴趣的事件新刊到货发生时自动获得通知。在C#中实现这种机制主要涉及两个核心角色被观察者Subject就像杂志社维护订阅者列表并提供注册/取消接口观察者Observer如同读者定义接收到通知时的处理逻辑// 基础接口定义 public interface IObserverT { void Update(T data); } public interface ISubjectT { void Register(IObserverT observer); void Unregister(IObserverT observer); void Notify(); }关键理解观察者模式实现了对象间的松耦合。杂志社不需要知道读者具体是谁、做什么工作只需在出版时广播通知。这种设计让系统更容易扩展——新增读者类型不会影响出版流程。2. C#中的三种实现方式对比2.1 经典手写实现这是最基础的方式适合理解原理。我们以温度监控系统为例public class TemperatureSensor : ISubjectfloat { private ListIObserverfloat _observers new(); private float _currentTemp; public void Register(IObserverfloat obs) _observers.Add(obs); public void Unregister(IObserverfloat obs) _observers.Remove(obs); public void Notify() { foreach(var obs in _observers) obs.Update(_currentTemp); } public void SetTemperature(float temp) { _currentTemp temp; Notify(); } } public class Display : IObserverfloat { public void Update(float temp) { Console.WriteLine($当前温度{temp}℃); } }避坑提示记得在Unregister时做null检查。我曾遇到过因未处理null导致的系统崩溃特别是在长时间运行的服务中。2.2 使用事件和委托C#原生的事件机制本质就是观察者模式。这种方式更符合语言习惯public class TemperatureSensor { public event Actionfloat OnTemperatureChanged; private float _currentTemp; public float CurrentTemperature { get _currentTemp; set { if(_currentTemp ! value) { _currentTemp value; OnTemperatureChanged?.Invoke(value); } } } } // 使用示例 var sensor new TemperatureSensor(); sensor.OnTemperatureChanged temp Console.WriteLine($温度更新{temp});优势分析语法更简洁内置线程安全机制无需手动管理订阅列表编译器会自动生成add/remove方法2.3 IObservable接口Reactive Extensions对于复杂事件处理System.IObservable提供了更强大的解决方案public class TemperatureSensor : IObservablefloat { private ListIObserverfloat _observers new(); public IDisposable Subscribe(IObserverfloat observer) { _observers.Add(observer); return new Unsubscriber(_observers, observer); } private class Unsubscriber : IDisposable { private ListIObserverfloat _observers; private IObserverfloat _observer; public void Dispose() { if(_observer ! null _observers.Contains(_observer)) _observers.Remove(_observer); } } public void ReportTemperature(float temp) { foreach(var obs in _observers) { obs.OnNext(temp); } } }适用场景对比表实现方式适用场景优点缺点经典实现教学、简单场景原理清晰需要手动管理事件委托大多数业务场景语法简洁、线程安全功能相对基础IObservable复杂事件流处理支持LINQ、强大操作符学习曲线陡峭3. 实战WPF中的温度监控系统让我们用WPF构建一个真实可用的温度监控界面。这个案例会展示如何将观察者模式应用于UI更新。3.1 模型层实现public class TemperatureModel : INotifyPropertyChanged { private float _temp; public float Temperature { get _temp; set { if(_temp ! value) { _temp value; OnPropertyChanged(); OnTemperatureChanged?.Invoke(value); } } } public event Actionfloat OnTemperatureChanged; public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } }3.2 ViewModel绑定public class MainViewModel { private readonly TemperatureModel _model; public MainViewModel(TemperatureModel model) { _model model; _model.OnTemperatureChanged temp { // 这里可以添加业务逻辑 CurrentTemp temp; }; } private float _currentTemp; public float CurrentTemp { get _currentTemp; set { _currentTemp value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } }3.3 视图层XAMLWindow x:ClassTemperatureMonitor.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml Title温度监控 Height200 Width300 StackPanel TextBlock Text{Binding CurrentTemp, StringFormat当前温度{0}℃} FontSize24 Margin10/ Button Content模拟温度变化 ClickOnTempChange Margin10/ /StackPanel /Window重要经验在WPF中一定要通过Dispatcher.Invoke更新UI线程。我曾遇到过因跨线程访问UI控件导致的诡异崩溃问题。4. 高级应用与性能优化4.1 弱引用事件处理内存泄漏是观察者模式的常见陷阱。当观察者不显式取消注册时会导致对象无法被GC回收public class WeakEventManager { private readonly ListWeakReferenceActionfloat _handlers new(); public void AddHandler(Actionfloat handler) { _handlers.Add(new WeakReferenceActionfloat(handler)); } public void Raise(float temp) { var toRemove new ListWeakReferenceActionfloat(); foreach(var wr in _handlers) { if(wr.TryGetTarget(out var handler)) { handler(temp); } else { toRemove.Add(wr); } } foreach(var dead in toRemove) { _handlers.Remove(dead); } } }4.2 异步事件处理当观察者处理较耗时为避免阻塞通知线程public event Funcfloat, Task OnTemperatureChangedAsync; // 触发方式 if(OnTemperatureChangedAsync ! null) { var handlers OnTemperatureChangedAsync.GetInvocationList() .CastFuncfloat, Task(); foreach(var handler in handlers) { _ Task.Run(() handler.Invoke(_currentTemp)); } }4.3 事件聚合器模式对于跨模块通信可以使用集中式事件管理器public class EventAggregator { private readonly ConcurrentDictionaryType, object _events new(); public TEvent GetEventTEvent() where TEvent : new() { return (TEvent)_events.GetOrAdd(typeof(TEvent), _ new TEvent()); } } // 定义强类型事件 public class TemperatureChangedEvent { public event Actionfloat OnEvent; public void Publish(float temp) OnEvent?.Invoke(temp); public void Subscribe(Actionfloat action) OnEvent action; public void Unsubscribe(Actionfloat action) OnEvent - action; }性能优化数据对比处理10000次通知方式内存占用执行时间线程安全普通事件1.2MB45ms否弱引用事件2.1MB68ms是异步事件1.3MB120ms部分事件聚合器1.8MB55ms是5. 常见问题与调试技巧5.1 事件不触发检查清单订阅检查确认操作确实执行使用调试器查看事件调用列表临时添加日志输出空值检查// 错误方式 OnTemperatureChanged(temp); // 可能NullReferenceException // 正确方式 OnTemperatureChanged?.Invoke(temp);线程上下文问题UI事件需要在主线程触发使用SynchronizationContext.Post跨线程5.2 内存泄漏排查使用内存分析工具检查在VS中使用Diagnostic Tools特别关注事件持有对象引用静态事件监听器未取消的定时器5.3 设计原则验证好的观察者模式实现应该通过以下检查被观察者不需要知道观察者的具体类型可以动态添加/移除观察者通知机制不依赖特定线程模型没有形成观察者之间的隐式依赖6. 模式变体与应用场景扩展6.1 推模型 vs 拉模型推模型事件发生时发送完整数据void Update(TemperatureData data);拉模型观察者根据需要获取数据void Update(ISubject sender); // 观察者调用 sender.GetTemperature()选择依据数据量小且固定 → 推模型数据量大或可选 → 拉模型需要最小化通知开销 → 拉模型6.2 中介者模式结合当观察者之间也需要通信时可以引入中介者public class MonitoringMediator { private readonly ListISensor _sensors new(); private readonly ListIDisplay _displays new(); public void RegisterSensor(ISensor sensor) { sensor.OnAlert OnSensorAlert; _sensors.Add(sensor); } private void OnSensorAlert(AlertData data) { foreach(var display in _displays) { display.ShowAlert(data); } } }6.3 C#特定优化技巧使用EventHandler 标准模式public event EventHandlerTemperatureChangedEventArgs TemperatureChanged; public class TemperatureChangedEventArgs : EventArgs { public float NewValue { get; } public float OldValue { get; } }避免频繁通知private float _temp; public float Temperature { get _temp; set { if(Math.Abs(_temp - value) 0.1) { // 阈值检查 _temp value; OnTemperatureChanged?.Invoke(value); } } }使用ValueChanged模式public class ValueChangedT { private T _value; public T Value { get _value; set { if(!EqualityComparerT.Default.Equals(_value, value)) { _value value; Changed?.Invoke(value); } } } public event ActionT Changed; }观察者模式在C#生态中的典型应用WPF/Silverlight数据绑定ASP.NET Core中间件管道实体框架变更跟踪SignalR客户端通知后台服务状态监控

相关新闻

【计算机JAVA毕业设计案例】面向高校的共享单车租赁服务管控平台 基于SpringBoot+Vue的校园单车用户租车记录与数据统计分析系统(程序+文档+讲解+定制)

【计算机JAVA毕业设计案例】面向高校的共享单车租赁服务管控平台 基于SpringBoot+Vue的校园单车用户租车记录与数据统计分析系统(程序+文档+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/28 15:28:35 阅读更多 →
利用HashSet去除重复元素

利用HashSet去除重复元素

练习1:利用HashSet去除重复元素 使用Scanner从键盘读取一行输入,去掉其中重复字符,打印出不同的那些字符 aaaabbbbccccdddd* 分析:* 1.创建Scanner对象* 2.创建HashSet对象,将字符存储,去掉重复* 3.将字符串转换为字符数组,获取每一个字符存储在HashSet集合中,自动去…

2026/7/28 15:28:34 阅读更多 →
信息系统项目管理师论文写作:质量管理模块高分指南

信息系统项目管理师论文写作:质量管理模块高分指南

1. 信息系统项目管理师论文写作要点解析从事IT项目管理十余年,我辅导过上百位学员准备信息系统项目管理师论文考试。质量管理作为九大知识领域中最常考的模块之一,其论文写作需要把握三个核心维度:理论框架的完整性、实践案例的真实性和改进措…

2026/7/28 15:27:34 阅读更多 →

最新新闻

ChatBI上线前必答的合规清单:权限、审计与敏感数据边界

ChatBI上线前必答的合规清单:权限、审计与敏感数据边界

导语 很多企业在落地ChatBI的时候,都会先纠结三个真实的问题:是不是只要把数据接入就能开放问数?普通业务人员会不会摸到不该看的敏感数据?出了问题怎么溯源是谁问了什么? 不少企业尝到了自然语言问数的甜头&#xff1…

2026/7/28 15:36:37 阅读更多 →
OpenClaw智能代理框架:金融分析、自动化与本地部署实战

OpenClaw智能代理框架:金融分析、自动化与本地部署实战

1. OpenClaw技能生态全景解析 OpenClaw作为新一代智能代理框架,早已突破单一搜索功能的局限,构建起覆盖多领域的技能矩阵。从GitHub社区讨论和实际部署案例来看,用户最常调用的核心技能主要集中在以下几个方向: 1.1 金融数据分析…

2026/7/28 15:36:37 阅读更多 →
岗位任务指南:一线销售、财务、供应链的高频决策如何数据化

岗位任务指南:一线销售、财务、供应链的高频决策如何数据化

导语 你一定在企业一线见过这些熟悉的场景: 一线销售每天打开CRM,面对上百个跟进线索,还是靠老经验判断优先跟进谁,常常花了大把时间在低意向客户身上,错过高转化的重点机会;月底的财务办公室永远亮着灯&am…

2026/7/28 15:36:37 阅读更多 →
语言到底在学什么?

语言到底在学什么?

很多人第一次学 C 语言,会觉得它很奇怪。 明明只是想学编程,结果一上来就遇到: 变量类型数组指针内存编译 看起来不像在“写程序”,更像在拆机器。 但这恰恰是 C 语言最有价值的地方。 这一期先用一句话讲清楚: C 语言…

2026/7/28 15:36:37 阅读更多 →
Switch-Toolbox:如何轻松编辑任天堂游戏文件的终极指南

Switch-Toolbox:如何轻松编辑任天堂游戏文件的终极指南

Switch-Toolbox:如何轻松编辑任天堂游戏文件的终极指南 【免费下载链接】Switch-Toolbox A tool to edit many video game file formats 项目地址: https://gitcode.com/gh_mirrors/sw/Switch-Toolbox 你是否曾经梦想过修改自己喜欢的Switch游戏,…

2026/7/28 15:36:37 阅读更多 →
uBlock Origin:终极浏览器内容拦截器与隐私保护解决方案指南

uBlock Origin:终极浏览器内容拦截器与隐私保护解决方案指南

uBlock Origin:终极浏览器内容拦截器与隐私保护解决方案指南 【免费下载链接】uBlock uBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean. 项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock 在当今数字时代&#xff0…

2026/7/28 15:35:37 阅读更多 →

日新闻

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:43 阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:43 阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:43 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/28 8:29:16 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/28 5:03:42 阅读更多 →

月新闻