当前位置: 首页 > news >正文

.NET 事件模型教程(二)

目录

  • 属性样式的事件声明
  • 单播事件和多播事件
  • 支持多播事件的改进

属性样式的事件声明

在第一节中,我们讨论了 .NET 事件模型的基本实现方式。这一部分我们将学习 C# 语言提供的高级实现方式:使用 add/remove 访问器声明事件。(注:本节内容不适用于 VB.NET。)

我们再来看看上一节中我们声明事件的格式:

        public event [委托类型] [事件名称];

这种声明方法,类似于类中的字段(field)。无论是否有事件处理程序挂接,它都会占用一定的内存空间。一般情况中,这样的内存消耗或许是微不足 道的;然而,还是有些时候,内存开销会变得不可接受。比如,类似 System.Windows.Forms.Control 类型具有五六十个事件,这些事件并非每次都会挂接事件处理程序,如果每次都无端的多处这么多的内存开销,可能就无法容忍了。

好在 C# 语言提供了“属性”样式的事件声明方式:

        public event [委托类型] [事件名称]
        {
            add { .... }
            remove { .... }
        }

如上的格式声明事件,具有 add 和 remove 访问器,看起来就像属性声明中的 get 和 set 访问器。使用特定的存储方式(比如使用 Hashtable 等集合结构),通过 add 和 remove 访问器,自定义你自己的事件处理程序添加和移除的实现方法。

Demo 1G:“属性”样式的事件声明。我首先给出一种实现方案如下(此实现参考了 .NET Framework SDK 文档中的一些提示)(限于篇幅,我只将主要的部分贴在这里):

        public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
        public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);

        // 注意:本例中的实现,仅支持“单播事件”。
        // 如需要“多播事件”支持,请参考 Demo 1H 的实现。

        // 为每种事件生成一个唯一的 object 作为键
        static readonly object StartWorkEventKey = new object();
        static readonly object EndWorkEventKey = new object();
        static readonly object RateReportEventKey = new object();

        // 使用 Hashtable 存储事件处理程序
        private Hashtable handlers = new Hashtable();

        // 使用 protected 方法而没有直接将 handlers.Add / handlers.Remove
        // 写入事件 add / remove 访问器,是因为:
        // 如果 Worker 具有子类的话,
        // 我们不希望子类可以直接访问、修改 handlers 这个 Hashtable。
        // 并且,子类如果有其他的事件定义,
        // 也可以使用基类的这几个方法方便的增减事件处理程序。
        protected void AddEventHandler(object eventKey, Delegate handler)
        {
            lock(this)
            {
                if (handlers[ eventKey ] == null)
                {
                    handlers.Add( eventKey, handler );
                }
                else
                {
                    handlers[ eventKey ] = handler;
                }
            }
        }

        protected void RemoveEventHandler(object eventKey)
        {
            lock(this)
            {
                handlers.Remove( eventKey );
            }
        }

        protected Delegate GetEventHandler(object eventKey)
        {
            return (Delegate) handlers[ eventKey ];
        }

        // 使用了 add 和 remove 访问器的事件声明
        public event StartWorkEventHandler StartWork
        {
            add { AddEventHandler(StartWorkEventKey, value); }
            remove { RemoveEventHandler(StartWorkEventKey); }
        }

        public event EventHandler EndWork
        {
            add { AddEventHandler(EndWorkEventKey, value); }
            remove { RemoveEventHandler(EndWorkEventKey); }
        }
        
        public event RateReportEventHandler RateReport
        {
            add { AddEventHandler(RateReportEventKey, value); }
            remove { RemoveEventHandler(RateReportEventKey); }
        }

        // 此处需要做些相应调整
        protected virtual void OnStartWork( StartWorkEventArgs e )
        {
            StartWorkEventHandler handler = 
                (StartWorkEventHandler) GetEventHandler( StartWorkEventKey );
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnEndWork( EventArgs e )
        {
            EventHandler handler =
                (EventHandler) GetEventHandler( EndWorkEventKey );

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRateReport( RateReportEventArgs e )
        {
            RateReportEventHandler handler =
                (RateReportEventHandler) GetEventHandler( RateReportEventKey );

            if (handler != null)
            {
                handler(this, e);
            }
        }

        public Worker()
        {
        }

        public void DoLongTimeTask()
        {
            int i;
            bool t = false;
            double rate;

            OnStartWork(new StartWorkEventArgs(MAX) );

            for (i = 0; i <= MAX; i++)
            {
                Thread.Sleep(1);
                t = !t;
                rate = (double)i / (double)MAX;

                OnRateReport( new RateReportEventArgs(rate) );
            }

            OnEndWork( EventArgs.Empty );
        }

细细研读这段代码,不难理解它的算法。这里,使用了名为 handlers 的 Hashtable 存储外部挂接上的事件处理程序。每当事件处理程序被“add”,就把它加入到 handlers 里存储;相反 remove 时,就将它从 handlers 里移除。这里取 event 的 key (开始部分为每一种 event 都生成了一个 object 作为代表这种 event 的 key)作为 Hashtable 的键。

[TOP]

 

单播事件和多播事件

在 Demo 1G 给出的解决方案中,你或许已经注意到:如果某一事件被挂接多次,则后挂接的事件处理程序,将改写先挂接的事件处理程序。这里就涉及到一个概念,叫“单播事件”。

所谓单播事件,就是对象(类)发出的事件通知,只能被外界的某一个事件处理程序处理,而不能被多个事件处理程序处理。也就是说,此事件只能被挂接一次,它只能“传播”到一个地方。相对的,就有“多播事件”,对象(类)发出的事件通知,可以同时被外界不同的事件处理程序处理。

打个比方,上一节开头时张三大叫一声之后,既招来了救护车,也招来了警察叔叔(问他是不是回不了家了),或许还有电视转播车(现场直播、采访张三为什么大叫,呵呵)。

多播事件会有很多特殊的用法。如果以后有机会向大家介绍 Observer 模式,可以看看 Observer 模式中是怎么运用多播事件的。(注:经我初步测试,字段形式的事件声明,默认是支持“多播事件”的。所以如果在事件种类不多时,我建议你采用上一节中所讲 的字段形式的声明方式。)

[TOP]

 

支持多播事件的改进

Demo1H,支持多播事件。为了支持多播事件,我们需要改进存储结构,请参考下面的算法:

        public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
        public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);

        // 为每种事件生成一个唯一的键
        static readonly object StartWorkEventKey = new object();
        static readonly object EndWorkEventKey = new object();
        static readonly object RateReportEventKey = new object();

        // 为外部挂接的每一个事件处理程序,生成一个唯一的键
        private object EventHandlerKey
        {
            get { return new object(); }
        }

        // 对比 Demo 1G,
        // 为了支持“多播”,
        // 这里使用两个 Hashtable:一个记录 handlers,
        // 另一个记录这些 handler 分别对应的 event 类型(event 的类型用各自不同的 eventKey 来表示)。
        // 两个 Hashtable 都使用 handlerKey 作为键。

        // 使用 Hashtable 存储事件处理程序
        private Hashtable handlers = new Hashtable();
        // 另一个 Hashtable 存储这些 handler 对应的事件类型
        private Hashtable events = new Hashtable();

        protected void AddEventHandler(object eventKey, Delegate handler)
        {
            // 注意添加时,首先取了一个 object 作为 handler 的 key,
            // 并分别作为两个 Hashtable 的键。

            lock(this)
            {
                object handlerKey = EventHandlerKey;
                handlers.Add( handlerKey, handler );
                events.Add( handlerKey, eventKey);
            }
        }

        protected void RemoveEventHandler(object eventKey, Delegate handler)
        {
            // 移除时,遍历 events,对每一个符合 eventKey 的项,
            // 分别检查其在 handlers 中的对应项,
            // 如果两者都吻合,同时移除 events 和 handlers 中的对应项。
            //
            // 或许还有更简单的算法,不过我一时想不出来了 :(

            lock(this)
            {
                foreach ( object handlerKey in events.Keys)
                {
                    if (events[ handlerKey ] == eventKey)
                    {
                        if ( (Delegate)handlers[ handlerKey ] == handler )
                        {
                            handlers.Remove( handlers[ handlerKey ] );
                            events.Remove( events[ handlerKey ] );
                            break;
                        }
                    }
                }
            }
        }

        protected ArrayList GetEventHandlers(object eventKey)
        {
            ArrayList t = new ArrayList();

            lock(this)
            {
                foreach ( object handlerKey in events.Keys )
                {
                    if ( events[ handlerKey ] == eventKey)
                    {
                        t.Add( handlers[ handlerKey ] );
                    }
                }
            }

            return t;
        }

        // 使用了 add 和 remove 访问器的事件声明
        public event StartWorkEventHandler StartWork
        {
            add { AddEventHandler(StartWorkEventKey, value); }
            remove { RemoveEventHandler(StartWorkEventKey, value); }
        }

        public event EventHandler EndWork
        {
            add { AddEventHandler(EndWorkEventKey, value); }
            remove { RemoveEventHandler(EndWorkEventKey, value); }
        }
        
        public event RateReportEventHandler RateReport
        {
            add { AddEventHandler(RateReportEventKey, value); }
            remove { RemoveEventHandler(RateReportEventKey, value); }
        }

        // 此处需要做些相应调整
        protected virtual void OnStartWork( StartWorkEventArgs e )
        {
            ArrayList handlers = GetEventHandlers( StartWorkEventKey );

            foreach(StartWorkEventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

        protected virtual void OnEndWork( EventArgs e )
        {
            ArrayList handlers = GetEventHandlers( EndWorkEventKey );

            foreach(EventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRateReport( RateReportEventArgs e )
        {
            ArrayList handlers = GetEventHandlers( RateReportEventKey );

            foreach(RateReportEventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

上面给出的算法,只是给你做参考,应该还有比这个实现更简单、更高效的方式。

相关文章:

  • SUP (SAP Mobile SDK 2.2) 连接 Sybase SQL Anywhere sample 数据库
  • 流的压缩与解压缩函数
  • Javascript 严格模式详解(转)
  • AngularJS的Hello World
  • 日志池
  • 电子病历,到底是用BS还是CS
  • Visual Studio (VSIX,项目模板 )制作
  • C#下实现的半角转与全角的互转
  • shell训练营Day19
  • 创建使用口令的角色,并分配给用户
  • 当Json数据中的key为Java关键字时,在定义实体类的时候不能对该字段进行声明,所以需要对字段进行特殊处理...
  • day16:计算文档中数字|检测两个文件的不同|检测网卡流量|批量杀死sh|检测是否开启80和是什么服务...
  • ruby多线程理解
  • 面试系列-高并发之synchronized
  • 解决fastJson无序问题
  • [case10]使用RSQL实现端到端的动态查询
  • HTTP中GET与POST的区别 99%的错误认识
  • JavaScript函数式编程(一)
  • Kibana配置logstash,报表一体化
  • MySQL QA
  • MySQL-事务管理(基础)
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • Redis 中的布隆过滤器
  • vue数据传递--我有特殊的实现技巧
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 两列自适应布局方案整理
  • 人脸识别最新开发经验demo
  • 算法---两个栈实现一个队列
  • 我的面试准备过程--容器(更新中)
  • 移动端唤起键盘时取消position:fixed定位
  • 应用生命周期终极 DevOps 工具包
  • 进程与线程(三)——进程/线程间通信
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​LeetCode解法汇总518. 零钱兑换 II
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • # .NET Framework中使用命名管道进行进程间通信
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (2.2w字)前端单元测试之Jest详解篇
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (二)fiber的基本认识
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (黑马C++)L06 重载与继承
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (七)Java对象在Hibernate持久化层的状态
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (原)Matlab的svmtrain和svmclassify
  • (转)关于多人操作数据的处理策略
  • (转)机器学习的数学基础(1)--Dirichlet分布
  • (轉)JSON.stringify 语法实例讲解