• .NET基础 (16)事件


    事件
    1 请解释事件的基本使用方法
    2 事件和委托有何联系
    3 如何设计一个带有很多事件的类型
    4 用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒

    事件
    1 请解释事件的基本使用方法

     事件时一种使对象或类能够提供通知的成员。客户端可以通过操作事件处理程序为相应的事件添加可执行代码。事件是一种特殊的委托。

    设计和使用事件的全过程可能需要包含下列几个步骤:

    • 如果需要的话,定义一个派生自System.EventArgs的参数类型
    • 在事件的管理类型中定义事件私有成员
    • 通知事件订阅者
    • 事件使用客户端订阅/取消定义

    示例:控制台输出事件管理和使用的示例

    首先定义一个控制台时间参数类型

        /// <summary>
        /// 自定义一个事件参数类型
        /// </summary>
        public class ConsoleEventArgs : EventArgs
        {
            //控制台输出的消息
            private String _message;
            /// <summary>
            /// 构造方法
            /// </summary>
            public ConsoleEventArgs():base()
            {
                _message = String.Empty;
            }
            /// <summary>
            /// 构造方法
            /// </summary>
            public ConsoleEventArgs(String message) : base()
            {
                _message = message;
            }
            /// <summary>
            /// 只读属性
            /// </summary>
            public String Message
            {
                get
                {
                    return _message;
                }
            }
        }

    随后编写控制台事件的管理类型:

        /// <summary>
        /// 管理控制台,在输出前发送输出事件
        /// </summary>
        public class ConsoleManager
        {
            //定义控制台事件成员对象
            public event EventHandler<ConsoleEventArgs> ConsoleEvent;
            /// <summary>
            /// 控制台输出
            /// </summary>
            /// <param name="message">用来构造事件参数</param>
            public void ConsoleOutput(String message)
            {
                //先发送事件
                ConsoleEventArgs args = new ConsoleEventArgs(message);
                SendConsoleEvent(args);
                //输出
                Console.WriteLine(message);
            }
            /// <summary>
            /// 负责发送事件
            /// </summary>
            /// <param name="args">事件参数</param>
            protected virtual void SendConsoleEvent(ConsoleEventArgs args)
            {
                //定义一个临时的引用变量,这样可以确保多线程访问时不会发生问题
                EventHandler<ConsoleEventArgs> temp = ConsoleEvent;
                if (temp != null)
                    temp(this, args);
            }
        }

    为了演示事件的订阅,定义了一个订阅控制台事件的日志类型,当控制台事件发生时,该类型对象将向对应的日志文件写入控制台输出的内容。

        /// <summary>
        /// 日志类型,订阅控制台输出事件
        /// </summary>
        public class Log
        {
            private const String LogFile="C:\TestLog.txt";
            public Log(ConsoleManager cm)
            {
                //订阅控制台输出事件
                cm.ConsoleEvent += WriteLog;
            }
            /// <summary>
            /// 事件处理方法,注意参数固定模式
            /// </summary>
            /// <param name="send">事件发送者</param>
            /// <param name="args">事件参数</param>
            private void WriteLog(object send, EventArgs args)
            {
                if(!File.Exists(LogFile))
                {
                    using (FileStream fs = File.Create(LogFile)) { }
                }
                FileInfo info = new FileInfo(LogFile);
                using (StreamWriter sw = info.AppendText())
                {
                    sw.WriteLine(DateTime.Now.ToString() + "|" +
                        send.ToString() + "|" +
                        ((ConsoleEventArgs)args).Message);
                }
            }
        }

    调用:

        class ConsoleEvent
        {
            static void Main(string[] args)
            {
                //测试事件
                ConsoleManager cm = new ConsoleManager();
                Log log = new Log(cm);
                cm.ConsoleOutput("测试控制台输出事件");
                cm.ConsoleOutput("测试控制台输出事件");
                cm.ConsoleOutput("测试控制台输出事件");
                Console.Read();
            }
        }


    2 事件和委托有何联系

     事件是一个委托类型。用Reflector查看上面例子的C#代码

    ConsoleEvent的反编译代码为:

    public event EventHandler<ConsoleEventArgs> ConsoleEvent
    {
        add
        {
            //省略……
        }
        remove
        {
            //省略……
        }
    }

    而EventHandler<ConsoleEventArgs>的定义为则为一个泛型委托:

    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

    事件有两个方法add_xxx和remove_xxx,分别用来注册和注销事件。

    查看上例中Log的构造函数的代码:

    public Log(ConsoleManager cm)
    {
        cm.ConsoleEvent += new EventHandler<ConsoleEventArgs>(this.WriteLog);
    }

    对应的IL代码为:

    .method public hidebysig specialname rtspecialname instance void .ctor(class MyTest.ConsoleManager cm) cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: nop 
        L_0007: nop 
        L_0008: ldarg.1 
        L_0009: ldarg.0 
        L_000a: ldftn instance void MyTest.Log::WriteLog(object, class [mscorlib]System.EventArgs)
        L_0010: newobj instance void [mscorlib]System.EventHandler`1<class MyTest.ConsoleEventArgs>::.ctor(object, native int)
        L_0015: callvirt instance void MyTest.ConsoleManager::add_ConsoleEvent(class [mscorlib]System.EventHandler`1<class MyTest.ConsoleEventArgs>)
        L_001a: nop 
        L_001b: nop 
        L_001c: ret 
    }


    3 如何设计一个带有很多事件的类型

     当某个类型包含较多的事件时,可以考虑把所有事件的委托链存储在一个集合之中,这样能做到有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。.NET的内建类型System.ComponentModel.EventHandlerList提供了这样一个存储事件集合的封装。

    示例:首先定义一个包含大量事件的类型,它使用一个EventHandlerList成员来存储所有事件,在类型构造和析构的时候需要对该成员初始化和析构

        /// <summary>
        /// 多事件类型
        /// </summary>
        public partial class MultiEventsClass:IDisposable
        {
            /// <summary>
            /// System.ComponentModel.EventHandlerList包含了一个委托链表的容器
            /// 实现了多事件存放在一个容器之中的包装
            /// EventHandlerList使用的是链表数据结构
            /// </summary>
            private EventHandlerList _events;
            
            //公共构造方法
            public MultiEventsClass()
            {
                _events = new EventHandlerList();
            }
            /// <summary>
            /// 释放EventHanlderList
            /// </summary>
            public void Dispose()
            {
                _events.Dispose();
            }
        }

    为了演示,这里只声明两个事件,而在实际情况中,多数事件类型可能会包含数十个事件

        /// <summary>
        /// 多事件类型
        /// </summary>
        public partial class MultiEventsClass : IDisposable
        {
            //下面为每一个需要实现的事件申明委托类型、订阅和取消定语方法、事件在集合中的键和触发事件方法
            //这样的定义和实际申明一个事件成员不同,这样做不会在一个新的MultiEventsClass中分配所有的事件委托链表的内存空间
            //这就是提高性能的关键
            //申明事件1
            #region event1
            //事件1的委托原型
            public delegate void Event1Handler(Object sender, EventArgs e);
            //这里是静态的字段,有效提高性能
            protected static readonly Object Event1Key = new object();        
            /// <summary>
            /// 一组订阅、取消订阅事件的方法
            /// 注意EventHandlerList并不提供线程同步,所以在add和remove方法前加上线程同步属性
            /// 读者可以采取lock机制来代替
            /// </summary>
            public event Event1Handler Event1
            {
                [MethodImpl(MethodImplOptions.Synchronized)]
                add
                {
                    _events.AddHandler(Event1Key, value);
                }
                [MethodImpl(MethodImplOptions.Synchronized)]
                remove
                {
                    _events.RemoveHandler(Event1Key, value);
                }
            }
            /// <summary>
            /// 触发事件1
            /// </summary>
            /// <param name="e"></param>
            protected virtual void OnEvent1(EventArgs e)
            {
                _events[Event1Key].DynamicInvoke(this, e);
            }
            /// <summary>
            /// 这个方法简单地触发事件1,以便于测试
            /// </summary>
            public void RiseEvent1()
            {
                OnEvent1(EventArgs.Empty);
            }
            #endregion
            //申明事件2
            #region event2
            //事件2的委托原型
            public delegate void Event2Handler(Object sender, EventArgs e);
            //这里是静态的字段,有效提高性能
            protected static readonly Object Event2Key = new object();
            /// <summary>
            /// 一组订阅、取消订阅事件的方法
            /// 注意EventHandlerList并不提供线程同步,所以在add和remove方法前加上线程同步属性
            /// 读者可以采取lock机制来代替
            /// </summary>
            public event Event2Handler Event2
            {
                [MethodImpl(MethodImplOptions.Synchronized)]
                add
                {
                    _events.AddHandler(Event2Key, value);
                }
                [MethodImpl(MethodImplOptions.Synchronized)]
                remove
                {
                    _events.RemoveHandler(Event2Key, value);
                }
            }
            /// <summary>
            /// 触发事件2
            /// </summary>
            /// <param name="e"></param>
            protected virtual void OnEvent2(EventArgs e)
            {
                _events[Event2Key].DynamicInvoke(this, e);
            }
            /// <summary>
            /// 这个方法简单地触发事件2,以便于测试
            /// </summary>
            public void RiseEvent2()
            {
                OnEvent2(EventArgs.Empty);
            }
            #endregion     
        }

    构造一个事件订阅类型:

        /// <summary>
        /// 构造一个订阅事件的类型
        /// </summary>
        public class Customer
        {
            public Customer(MultiEventsClass events)
            {
                //订阅事件1
                events.Event1 += Event1Handler;
                //订阅事件2
                events.Event2 += Event2Handler;
            }
            /// <summary>
            /// 事件1回调方法
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="args"></param>
            private void Event1Handler(object sender, EventArgs args)
            {
                Console.WriteLine("事件1触发");
            }
            /// <summary>
            /// 事件2回调方法
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="args"></param>
            private void Event2Handler(object sender, EventArgs args)
            {
                Console.WriteLine("事件2触发");
            }
        }

    调用:

        class MainClass
        {
            static void Main(string[] args)
            {
                //测试事件的触发
                using (MultiEventsClass c = new MultiEventsClass())
                {
                    Customer customer = new Customer(c);
                    c.RiseEvent1();
                    c.RiseEvent2();
                } 
                Console.Read();
            }
        }

    输出:

    事件1触发
    事件2触发


    4 用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒

     设计思路:猫类负责并维护一个猫叫事件,主人和老鼠则需要订阅猫叫这一事件,这样能够保证猫叫时执行相应的动作。

    首先设计猫的类型,该类型是猫叫事件的管理者。这里为猫叫事件定义了一个参数类型,来说明发出声音的猫的名字:

        #region cat
        /// <summary>
        /// 猫类型
        /// 维护猫叫事件
        /// </summary>
        public class Cat
        {
            /// <summary>
            /// 猫名
            /// </summary>
            private String _name;
            /// <summary>
            /// 猫叫的事件
            /// </summary>
            public event EventHandler<CatCryEventArgs> CatCryEvent;
            /// <summary>
            /// 构造方法
            /// </summary>
            /// <param name="name"></param>
            public Cat(String name)
            {
                _name = name;
            }
            /// <summary>
            /// 触发猫叫事件
            /// </summary>
            public void CatCry()
            {
                CatCryEventArgs args = new CatCryEventArgs(_name);
                Console.WriteLine(args);
                CatCryEvent(this, args);
            }
        }
        /// <summary>
        /// 猫叫事件的参数
        /// </summary>
        public class CatCryEventArgs : EventArgs
        {
            //发出叫声的猫的名字
            private String _catname;
            public CatCryEventArgs(String catname)
                : base()
            {
                _catname = catname;
            }
            /// <summary>
            /// 输出参数内容
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return _catname + "叫了";
            }
        }
        #endregion

    然后是主人和老鼠类型定义,这两个类型都是猫叫事件的订阅者。

        #region Master
        /// <summary>
        /// 主人类型
        /// </summary>
        public class Master
        {
            /// <summary>
            /// 主人名字
            /// </summary>
            private String _name;
            /// <summary>
            /// 构造方法,订阅事件
            /// </summary>
            /// <param name="name"></param>
            /// <param name="cat"></param>
            public Master(String name, Cat cat)
            {
                _name = name;
                cat.CatCryEvent += CatCryHandler;
            }
            /// <summary>
            /// 猫叫事件处理方法
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="args"></param>
            private void CatCryHandler(object sender, CatCryEventArgs args)
            {
                WakeUp();
            }
            /// <summary>
            /// 惊醒方法
            /// </summary>
            private void WakeUp()
            {
                Console.WriteLine(_name + "醒了");
            }
        }
        #endregion
    
        #region mouse
        /// <summary>
        /// 老鼠类型
        /// </summary>
        public class Mouse
        {
            /// <summary>
            /// 老鼠名字
            /// </summary>
            private String _name;
    
            public Mouse(String name, Cat cat)
            {
                _name = name;
                cat.CatCryEvent += CatCryHandler;
            }
            /// <summary>
            /// 猫叫事件处理方法
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="args"></param>
            private void CatCryHandler(object sender, CatCryEventArgs args)
            {
                Run();
            }
            /// <summary>
            /// 逃跑方法
            /// </summary>
            private void Run()
            {
                Console.WriteLine(_name + "逃走了");
            }
        }
        #endregion

    调用:

        class MainClass
        {
            static void Main(string[] args)
            {
                //开始模拟场景
                Console.WriteLine("开始模拟");
                Cat cat = new Cat("汤姆猫");
                Mouse mouse1 = new Mouse("米老鼠", cat);
                Mouse mouse2 = new Mouse("杰瑞鼠", cat);
                Master master = new Master("Jesse", cat);
                cat.CatCry();
                Console.Read();
            }
        }

    输出:

    开始模拟
    汤姆猫叫了
    米老鼠逃走了
    杰瑞鼠逃走了
    Jesse醒了


    转载请注明出处:

    作者:JesseLZJ
    出处:http://jesselzj.cnblogs.com

  • 相关阅读:
    图片采集
    资源(先备着)
    Memories of Vladimir Arnold 摘录
    Tribute to Vladimir Arnold 摘录
    Textbooks, Testing, Training: How We Discourage Thinking 笔记
    每天都有杰出数学家去世?(挖坑)
    I'm angry——人教B版选修2-3上的一个错误
    How to Study as a Mathematics Major 的笔记
    读《桑榆忆往》做的一点笔记
    《近代欧氏几何学》笔记
  • 原文地址:https://www.cnblogs.com/jesselzj/p/4799489.html
Copyright © 2020-2023  润新知