.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

转载于:https://www.cnblogs.com/jesselzj/p/4799489.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值