《Effective C#》读书笔记——条目25:用事件模式实现通知<使用C#表达设计>

简介:

 .NET中的事件其实就是一个观察者模式(Observer Pattern)的一个语法上的快捷实现(更多可以参考:使用委托和事件实现观察者模式)。事件是一种内建的委托,用来为事件处理函数提供类型安全的方法签名。事件就是对象将信息告知观察者的方式。

 

1.发布者定义事件

  我们来看一个例子,有一个日志类,将应用程序需要分发的信息发送个各个侦听者,这些侦听者可以是控制域、系统日志、数据库等等,首先定义一个在事件触发中负责传递消息的事件参数类:

复制代码
 1         public class LoggerEventArgs : EventArgs
 2         {
 3             public string message { get; private set; }
 4             public int Priority { get; private set; }
 5 
 6             public LoggerEventArgs(int p, string m)
 7             {
 8                 this.Priority = p;
 9                 this.message = m;
10             }
11         }
复制代码

 然后是日志类本身:

复制代码
 1     public class Logger
 2     {
 3         static Logger()
 4         {
 5             theOnly = new Logger();
 6         }
 7 
 8         private Logger()
 9         {
10         }
11 
12         private static Logger theOnly = null;
13         public static Logger Singleton
14         {
15             get { return theOnly; }
16         }
17         //定义事件
18         public event EventHandler<LoggerEventArgs> Log;
19         //在这里增加消息和日志
20         public void AddMsg(int priority, string msg)
21         {
22             //这里引用临时变量是一个重要的安全措施,可预防多线程环境中的竞争条件
23             //若是没有引用的副本,客户代码可能会在if判断语句和事件处理函数之间移
24             //除事件处理函数,而复制引用之后即可避免这种情况
25             EventHandler<LoggerEventArgs> l = Log;
26             if (l != null)
27                 l(this, new LoggerEventArgs(priority, msg));
28         }
29     }
复制代码

在这里AddMsg()是触发事件的方法,LoggerEventArgs类中定义了事件的优先级和消息内容,委托则为事件处理函数定义了签名。在Logger类内部,事件自动Log定义了事件处理函数。编译器看到这个字段后会自动创建对应的Add和Remove操作符,编译器生成的代码和下面类似:

复制代码
 1         public class Logger
 2         {
 3             private EventHandler<LoggerEventArgs> log;
 4 
 5             public event EventHandler<LoggerEventArgs> Log
 6             {
 7                 add { log = log + value; }
 8                 remove { log = log - value; }
 9             }
10             public void AddMsg(int priority, string msg)
11             {
12                 EventHandler<LoggerEventArgs> l = log;
13                 if (l != null)
14                     l(this, new LoggerEventArgs(priority, msg));
15             }
16         }
复制代码

 

或者我们可以直接查看IL代码:

 

2.侦听者订阅事件

  我们可以把日志信息订阅到标准错误控制台输出:

复制代码
 1     class ConsoleLogger
 2     {
 3         static ConsoleLogger()
 4         {
 5             Logger.Singleton.Log += (sender, msg) =>
 6             {
 7                 Console.Error.WriteLine("{0}:\t{1}", msg.Priority.ToString(), msg.message);
 8             };
 9         }
10     }
复制代码

或者是直接将日志记录到系统日志中:

View Code

 运行程序:

1             ConsoleLogger c = new ConsoleLogger();
2             EventLogger.Evensource = "事件源?";
3             EventLogger ee = new EventLogger();
4             Logger.Singleton.AddMsg(10086, "这个Logger类创建的日志");

 

3.动态创建事件对象

   前面的Logger类只包含了一个事件,但有时候也有一些类(Windows控件)包含的事件数量非常多,这种情况下,为每个事件都定义一个字段的做法会显得比较臃肿。在某些情况下,只要很少的事件会在程序中真正起到作用,这时候我们需要根据运行时的需要来动态创建事件对象。

   根据前面的Logger类我们对其进行扩展,想Logger类中添加子系统,可以为每个子系统创建一个事件。客户则会注册到子系统中的事件。扩展后的Logger类包含了System.ComponentModel.EventHandlerList容器,存放所有的事件对象。

View Code

 

   前面的EventHandlerList没有提供泛型的版本,所以其中有很多的类型转换操作。当客户代码关联到一个特定的子系统上,新的事件就会被创建。对于同一个子系统的后续请求会获取相同的事件对象。如果我们的类中有大量的事件,应该考虑使用这种事件处理函数集合。仅当客户代码真正注册有事件处理函数时,才会创建事件成员。在.NET Framework内部,System.Windows.Forms.Control类使用了一种复杂的方式,进而隐藏所有事件字段操作的复杂性。

  EventHandlerList没有提供内建的泛型实现,我们可以基于Dictionary自行构造,泛型版本降低了类型转换的工作,但也增加了一些用来映射事件的代码,具体使用哪种方式,可以根据实际情况来考量:

View Code

 

小节

事件提供了一种标准的机制来通知侦听者。.NET的事件模式使用了事件语法来实现观察者模式。任意数量的客户对象都可以将自己的处理函数注册到事件上,然后处理这些事件。这些客户对象不需要在编译器就给出,事件也不必非有订阅者才能正常工作。在C#中使用事件可以降低发送者和可能的通知接收者之间的耦合。发送者完全可以独立于接收者进行开发。事件是实现广播类型行为的标准方式。

本文转自gyzhao博客园博客,原文链接:http://www.cnblogs.com/IPrograming/archive/2013/01/18/EffectiveCSharp_25.html ,如需转载请自行联系原作者

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
JSON C# 数据格式
【Azure Function】C#独立工作模式下参数类型 ServiceBusReceivedMessage 无法正常工作
Cannot convert input parameter 'message' to type 'Azure.Messaging.ServiceBus.ServiceBusReceivedMessage' from type 'System.String'.
299 73
|
10月前
|
机器学习/深度学习 监控 算法
局域网行为监控软件 C# 多线程数据包捕获算法:基于 KMP 模式匹配的内容分析优化方案探索
本文探讨了一种结合KMP算法的多线程数据包捕获与分析方案,用于局域网行为监控。通过C#实现,该系统可高效检测敏感内容、管理URL访问、分析协议及审计日志。实验表明,相较于传统算法,KMP在处理大规模网络流量时效率显著提升。未来可在算法优化、多模式匹配及机器学习等领域进一步研究。
260 0
|
设计模式 开发框架 前端开发
MVC 模式在 C# 中的应用
MVC(Model-View-Controller)模式是广泛应用于Web应用程序开发的设计模式,将应用分为模型(存储数据及逻辑)、视图(展示数据给用户)和控制器(处理用户输入并控制模型与视图交互)三部分,有助于管理复杂应用并提高代码可读性和维护性。在C#中,ASP.NET MVC框架常用于构建基于MVC模式的Web应用,通过定义模型、控制器和视图,实现结构清晰且易维护的应用程序。
331 2
|
C#
C#一分钟浅谈:委托与事件的实现方式
本文详细介绍了C#编程中委托与事件的基础知识及应用场景。首先解释了委托的概念,包括定义与使用方法;接着介绍了事件这一基于委托的特殊类型,展示了如何在类中定义事件及跨类订阅与处理事件;最后讨论了常见问题如事件未处理异常、重复订阅及内存泄漏等,并提出了相应的解决方案。通过本文,读者将全面掌握委托与事件的使用技巧,提升应用程序的设计与开发水平。
458 7
由浅入深理解C#中的事件
由浅入深理解C#中的事件
329 19
|
设计模式 程序员 C#
C# 使用 WinForm MDI 模式管理多个子窗体程序的详细步骤
WinForm MDI 模式就像是有超能力一般,让多个子窗体井然有序地排列在一个主窗体之下,既美观又实用。不过,也要小心管理好子窗体们的生命周期哦,否则一不小心就会出现一些意想不到的小bug
1611 0
|
C++ 安全 存储
C++智能指针解析
C++智能指针解析
336 0
C++智能指针解析
|
存储 算法 C++
Hash与布隆过滤器
Hash与布隆过滤器
203 0
Hash与布隆过滤器
WPF/C#:程序关闭的三种模式
WPF/C#:程序关闭的三种模式
368 3
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
868 0
下一篇
开通oss服务