• C#基础篇——事件


    前言

    在本章中,主要是借机这个C#基础篇的系列整理过去的学习笔记、归纳总结并更加理解透彻。

    在上一篇文章,我们已经对委托有了进一步了解,委托相当于用方法作为另一方法参数,同时,也可以实现在两个不能直接调用的方法中做桥梁。

    下面我们来回顾一下委托的例子。

        public delegate void ExecutingDelegate(string name);
    
        public class ExecutingManager
        {
            public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
            {
                ToExecuting(name);
            }
        }
        private static void StartExecute(string name)
        {
            Console.WriteLine("开始执行:" + name);
        }
    
        private static void EndExecute(string name)
        {
            Console.WriteLine("结束执行:" + name);
        }
    
        static void Main(string[] args)
        {
            ExecutingManager exec = new ExecutingManager();
            exec.ExecuteProgram("开始。。。", StartExecute);
            exec.ExecuteProgram("结束。。。", EndExecute);
            Console.ReadKey();
        }
    

    根据上述的示例,再利用上节学到的知识,将多个方法绑定到同一个委托变量实现多播,该如何做呢?

    再次修改代码:

        static void Main(string[] args)
        {
            ExecutingManager exec = new ExecutingManager();
            ExecutingDelegate executingDelegate;
            executingDelegate = StartExecute;
            executingDelegate += EndExecute;
            exec.ExecuteProgram("yuan", executingDelegate);
    
            Console.ReadKey();
        }
    

    但是,此刻我们发现是不是可以将实例化声明委托的变量封装到ExecutingManager类中,这样是不是更加方便调用呢?

        public class ExecutingManager
        {
            /// <summary>
            /// 在 ExecutingManager 类的内部声明 executingDelegate 变量
            /// </summary>
            public ExecutingDelegate executingDelegate;
            public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
            {
                ToExecuting(name);
            }
        }
    
        static void Main(string[] args)
        {
            ExecutingManager exec = new ExecutingManager();
            exec.executingDelegate = StartExecute;
            exec.executingDelegate += EndExecute;
            exec.ExecuteProgram("yuan", exec.executingDelegate);
            Console.ReadKey();
        }
    

    写到这里了,这样做没有任何问题,但我们发现这条语句很奇怪。在调用exec.ExecuteProgram方法的时候,再次传递了exec的executingDelegate字段, 既然如此,我们何不修改 ExecutingManager类成这样:

        public class ExecutingManager
        {
            /// <summary>
            /// 在 GreetingManager 类的内部声明 delegate1 变量
            /// </summary>
            public ExecutingDelegate executingDelegate;
            public void ExecuteProgram(string name)
            {
                if (executingDelegate != null) // 如果有方法注册委托变量
                {
                    executingDelegate(name); // 通过委托调用方法
                }
            }
        }
    
        static void Main(string[] args)
        {
            ExecutingManager exec = new ExecutingManager();
            exec.executingDelegate = StartExecute;
            exec.executingDelegate += EndExecute;
            exec.ExecuteProgram("yuan");
            Console.ReadKey();
        }
    

    这样再看,发现调用一下就更加简洁了。

    正文

    在日常生活中,我们可能都会遇到这样的各种各样的事情,而对于这些事情我们都会采取相应的措施。比如,当你要给一个女神过生日的时候,你就可以给她送礼物。而这种情况,在C#开发中,就相当于过生日被当作事件来对待,而送礼物就是事件做出的响应。

    当女神过生日的时候,女神就会发布生日事件,而你就会接受到这个事件的通知,并做出响应的处理(送礼物等骚操作)。其中,触发这个事件的对象我们可称之为事件发布者,而捕获这个事件并做出相应处理的称之为事件订阅者,我们可以看出,女神就是充当了发布者,而你自己则充当了订阅者。

    这里由生日事件引申出两类角色,即事件发布者事件订阅者

    开始

    1.发布者/订阅者模式

    在开发中,我们是否遇到这样的情景,当一个特定的程序事件发生时,其他程序部分可以得到该事件注册发生通知。

    发布者定义一系列事件,并提供一个注册方法;订阅者向发布者注册,并提供一个可被回调的方法,也就是事件处理程序;当事件被触发的时候,订阅者得到通知,而订阅者所提交的所有方法会被执行。

    • 发布者:发布某个事件的类或结构,其他类可以在该事件发生时得到通知。
    • 订阅者:注册并在事件发生时得到通知的类或结构。
    • 事件处理程序:由订阅者注册到事件的方法,在发布者触发事件时执行。事件处理程序方法可以定义在事件所在的类或结果中,也可以定义在不同的类或结构中。
    • 触发事件:调用事件的术语。当事件触发时,所有注册到它的方法都会被一次调用。

    2.基本使用

            /// <summary>
            /// 先自定义一个委托
            /// </summary>
            /// <param name="oldPrice"></param>
            /// <param name="newPrice"></param>
            public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
            /// <summary>
            /// 这个一个发布者
            /// </summary>
            public class IPhone
            {
                decimal price;
                /// <summary>
                /// 定义一个事件
                /// event 用来定义事件
                /// PriceChangedHandler委托类型,事件需要通过委托来调用订阅者需要的方法
                /// </summary>
                public event PriceChangedHandler PriceChanged;
              
                public decimal Price
                {
                    get { return price; }
                    set
                    {
                        if (price == value)
                            return;
                        decimal oldPrice = price;
                        price = value;             // 如果调用列表不为空,则触发。            
                        if (PriceChanged != null)   //用来判断事件是否被订阅者注册过
                            PriceChanged(oldPrice, price);   //调用事件
                    }
                }
            }
            /// <summary>
            /// 这个一个订阅者
            /// </summary>
            /// <param name="oldPrice"></param>
            /// <param name="price"></param>
            static void iPhone_PriceChanged(decimal oldPrice, decimal price)
            {
                Console.WriteLine("618促销活动,全场手机 只卖 " + price + " 元, 原价 " + oldPrice + " 元,快来抢!");
            }
            static void Main()
            {
                ///实例化一个发布者类
                IPhone phone = new IPhone()
                {
                    Price = 5288
                };         // 订阅事件   
                phone.PriceChanged += iPhone_PriceChanged;          //完成事件的注册 调整价格(事件发生)    
                phone.Price = 3999;                                //激发事件,并调用事件
                Console.ReadKey();
            }
    

    输出:

    618促销活动,全场手机 只卖 3999 元, 原价 5288 元,快来抢!

    3.解析

    1. 委托类型声明:事件与事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述。
    2. 事件声明:使用关键字evet来声明一个事件,当声明的事件为一个public时,称为发布了一个事件。
    3. 事件注册:订阅者通过+=操作符来注册事件,并提供一个事件处理程序。
    4. 事件处理程序: 订阅者向事件注册的方法,它可以是显示命名的方法、匿名方法或者Lambda表达式
    5. 触发事件:发布者用来调用事件的代码

    4.语法

    事件的声明语法:

    //声明一个事件
    public [static] event EventHandler EventName;
    //声明多个同类型的事件
    public [static] event EventHandler EventName1, EventName2, EventName3;
    

    事件必须声明在类或结构中,因为事件它不是一个类型,它是一个类或者结构中的一员。

    在事件被触发之前,可以通过和null做比较,判断是否包含事件注册处理程序。因为事件成员被初始化默认是null。

    委托类型EventHandler是声明专门用来事件的委托。事件提供了对委托的结构化访问;也即是无法直接访问事件中的委托。

    5.用法

    img

    查看源码:

    事件的标准模式就是System命名空间下声明的EventHandler委托类型。

    EventArgs是System下的一个类,如下:

    using System.Runtime.InteropServices;
    
    namespace System
    {
        [Serializable]
        [ComVisible(true)]
        [__DynamicallyInvokable]
        public class EventArgs
        {
            [__DynamicallyInvokable]
            public static readonly EventArgs Empty = new EventArgs();
    
            [__DynamicallyInvokable]
            public EventArgs()
            {
            }
        }
    }
    

    根据EventArgs源码看出,EventArgs本身无法保存和传递数据的。

    如果想保存和传递数据,可以实现一个EventArgs的派生类,然后定义相关的字段来保存和传递参数。

        public class IPhone
        {
            decimal price;
            /// <summary>
            /// 使用EventHandler定义一个事件
            /// </summary>
            public event EventHandler PriceChanged;
            protected virtual void OnPriceChanged()
            {
                if (PriceChanged != null)
                    PriceChanged(this, null);
            }
            public decimal Price
            {
                get { return price; }
                set
                {
                    if (price == value) return;
                    decimal oldPrice = price; 
                    price = value;             // 如果调用列表不为空,则触发。      
                    if (PriceChanged != null)  // //用来判断事件是否被订阅者注册过
                        OnPriceChanged();
                }
            }
        }
        /// <summary>
        /// 这个一个订阅者
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void iphone_PriceChanged(object sender, EventArgs e)
        {
            Console.WriteLine("年终大促销,快来抢!");
        }
        static void Main()
        {
            IPhone phone = new IPhone()
            {
                Price = 5288M
            };         // 订阅事件  
            phone.PriceChanged += iphone_PriceChanged;
            // 调整价格(事件发生)   
            phone.Price = 3999;
            Console.ReadKey();
        }
    

    通过扩展EventHanlder来传递数据

    img

    System下另有泛型EventHandler类。由此,这里我们可以将派生于EventArgs的类作为类型参数传递过来,这样,既可以获得派生类保存的数据。

        ///扩展类
        public class PriceChangedEventArgs : System.EventArgs
        {
            public readonly decimal OldPrice;
            public readonly decimal NewPrice;
            public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
            {
                OldPrice = oldPrice;
                NewPrice = newPrice;
            }
        }
        public class IPhone
        {
            decimal price;
            public event EventHandler<PriceChangedEventArgs> PriceChanged;
            protected virtual void OnPriceChanged(PriceChangedEventArgs e)
            {
                if (PriceChanged != null)
                    PriceChanged(this, e);
            }
            public decimal Price
            {
                get { return price; }
                set
                {
                    if (price == value) return;
                    decimal oldPrice = price;
                    price = value;             // 如果调用列表不为空,则触发。      
                    if (PriceChanged != null)
                        OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
                }
            }
        }
        static void iphone_PriceChanged(object sender, PriceChangedEventArgs e)
        {
            Console.WriteLine("618促销活动,全场手机 只卖 " + e.NewPrice + " 元, 原价 " + e.OldPrice + " 元,快来抢!");
        }
        static void Main()
        {
            IPhone phone = new IPhone()
            {
                Price = 5288M
            };         // 订阅事件  
            phone.PriceChanged += iphone_PriceChanged;
            // 调整价格(事件发生)   
            phone.Price = 3999;
            Console.ReadKey();
        }
    

    输出

    618促销活动,全场手机 只卖 3999 元, 原价 5288 元,快来抢!

    6.移除事件

    可以利用 -= 运算符处理程序从事件中移除,当程序处理完后,可以将事件从中把它移除掉。

            class Publiser
            {
                public event EventHandler SimpleEvent;
    
                public void RaiseTheEvent()
                {
                    SimpleEvent(this, null);
                }
            }
    
            class Subscriber
            {
                public void MethodA(object o, EventArgs e) { Console.WriteLine("A"); }
                public void MethodB(object o, EventArgs e) { Console.WriteLine("B"); }
            }
    
    
            static void Main(string[] args)
            {
                Publiser p = new Publiser();
                Subscriber s = new Subscriber();
    
                p.SimpleEvent += s.MethodA;
                p.SimpleEvent += s.MethodB;
                p.RaiseTheEvent();
    
                Console.WriteLine("
    移除B事件处理程序");
                p.SimpleEvent -= s.MethodB;
                p.RaiseTheEvent();
    
                Console.ReadKey();
            }
    

    输出:

    img

    7.事件访问器

    运算符+= 、-=事件允许的唯一运算符。这些运算符是有预定义的行为。然而,我们可以修改这些运算符的行为,让事件执行任何我们希望定义的代码。

    可以通过为事件定义事件访问器,来控制事件运算符+=、-=运算符的行为

    1. 两个访问器: add 和 remove
    2. 声明事件的访问器看上去和声明一个熟悉差不多。

    下面示例演示了具有访问器的声明.两个访问器都有叫做value的隐式值参数,它接受实例或静态方法的引用

    public event EventHandler Elapsed
    {
        add
        {
            //... 执行+=运算符的代码
        }
    
         remove
         {
            //... 执行-=运算符的代码
         }
    
    }
    

    声明了事件访问器后,事件不包含任何内嵌委托对象.我们必须实现自己的机制来存储和移除事件的方法。

    事件访问器表现为void方法,也就是不能使用会返回值的return语句。

    示例:

            //声明一个delegate
            delegate void EventHandler();
    
            class MyClass
            {
                //声明一个成员变量来保存事件句柄(事件被激发时被调用的delegate)
                private EventHandler m_Handler = null;
    
                //激发事件
                public void FireAEvent()
                {
                    if (m_Handler != null)
                    {
                        m_Handler();
                    }
                }
    
                //声明事件
                public event EventHandler AEvent
                {
                    //添加访问器
                    add
                    {
                        //注意,访问器中实际包含了一个名为value的隐含参数
                        //该参数的值即为客户程序调用+=时传递过来的delegate
                        Console.WriteLine("AEvent add被调用,value的HashCode为:" + value.GetHashCode());
                        if (value != null)
                        {
                            //设置m_Handler域保存新的handler
                            m_Handler = value;
                        }
                    }
    
                    //删除访问器
                    remove
                    {
                        Console.WriteLine("AEvent remove被调用,value的HashCode为:" + value.GetHashCode());
                        if (value == m_Handler)
                        {
                            //设置m_Handler为null,该事件将不再被激发
                            m_Handler = null;
                        }
                    }
    
                }
    
            }
    
            static void Main(string[] args)
            {
                MyClass obj = new MyClass();
                //创建委托
                EventHandler MyHandler = new EventHandler(MyEventHandler);
                MyHandler += MyEventHandle2;
                //将委托注册到事件
                obj.AEvent += MyHandler;
                //激发事件
                obj.FireAEvent();
                //将委托从事件中撤销
                obj.AEvent -= MyHandler;
                //再次激发事件
                obj.FireAEvent();
    
    
                Console.ReadKey();
            }
            //事件处理程序
            static void MyEventHandler()
            {
                Console.WriteLine("This is a Event!");
            }
    
            //事件处理程序
            static void MyEventHandle2()
            {
                Console.WriteLine("This is a Event2!");
            }
    

    输出:

    img

    总结

    1. 这节对事件的基本使用,以及事件的标准语法、事件访问器等多个地方进行说明,大致可以了解和掌握事件的基本使用。
    2. 结合上一篇的委托和这一节的事件,委托和事件我们大概掌握了基本用法。并加以实践,结合实际开发,应用其中。
    3. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。

    参考 文档 《C#图解教程》

    注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件

  • 相关阅读:
    Windows激活客户端 已停止工作
    sevices.msc灰色处理办法
    日期求星期(java)蓝桥杯
    全排列筛选(java)
    日期类的使用(java)蓝桥杯
    蓝桥杯加法变乘法(java)
    搭积木(java)蓝桥杯
    Ftp commands and options
    随机long(Random long/NextLong)
    Windows Azure Storage Explorer List
  • 原文地址:https://www.cnblogs.com/i3yuan/p/13081631.html
Copyright © 2020-2023  润新知