C#事件

  • 2020 年 12 月 28 日
  • 笔记

C#事件

想起来这个月还没写博客,随便混更一下吧

事件

事件(event)指的是一些能够发生的事情,它是一种使对象或者类能够提供通知的成员。举个例子,你打开了手机,点击了一下“信息”,然后就会弹出信息的窗口。“点击”这个动作就是一个事件,它向CPU传达了一个消息,打开信息的窗口,然后CPU处理这个信息,打开了“信息”的窗口。
经过这番解释,你应该大致知道事件这个模型的组成了:事件拥有者(event source)、事件本身(event)、事件响应者(event subscriber)、事件处理器(event handler)和事件订阅。事件拥有者,顾名思义,就是这个事件是谁的,事件本身同理;事件响应者是指接收信息的一方;事件处理器是对事件做出回应,一般是一个方法,而且是一个回调方法;事件订阅就是把事件处理器与事件关联在一起,如果订阅了这个事件,当它发生时就会调用事件处理器的方法。
其实事件在手机应用开发当中十分常见,不过Java里面没有事件这种成员,Java使用接口来实现事件。

使用事件

在放代码之前,先要添加一个引用,我们要用到一个新的程序集。具体方法为:对着引用右键,点击添加引用,在程序集里面搜索System.Windows.Forms,勾选添加。
添加引用

然后再来看例子:

using System;
using System.Windows.Forms;

namespace EventPractice
{
    class Program
    {
        static void Main(string[] args)
        {
            MyForm form = new MyForm();
            form.ShowDialog();
        }
    }

    class MyForm : Form                                 //继承,MyForm这个类可以使用System.Windows.Forms.Form里面public和protected修饰的成员
    {
        private TextBox textBox;                        //TextBox和Button都是System.Windows.Forms里面的类
        private Button button;

        public MyForm()                                 //构造函数,初始化成员
        {
            this.textBox = new TextBox();
            this.button = new Button();

            this.Controls.Add(this.button);             //添加控件
            this.Controls.Add(this.textBox);

            this.button.Click += this.ButtonClicked;    //Click是一个在System.Windows.Forms里面定义的事件,button是事件的拥有者,MyForm的对象form是事件的响应者
                                                        //+=操作符订阅该事件,ButtonClicked是事件处理器,在下文定义,但是记得不要在ButtonClicked后面加括号
            this.button.Text = "Say Hello!";

            this.button.Top = 50;
        }

        private void ButtonClicked(object sender, EventArgs e)   //事件处理器,接收两个参数:sender是事件源,e是事件参数,这是C#自动生成的事件处理器
        {                                                        //至于为什么这么写,是因为它隐藏了一个“约定”——委托(delegate)
            this.textBox.Text = "Hello World!";                  //点击按钮会出现这句话
        }
    }
}

先看看跑出来的结果吧:
事件结果1

这个例子用到了C#的WinForm,因为它比较容易解释事件。看MyForm这个类里面,有一个叫Click的事件,它也十分直观,就是在点击的时候做出一些反应,我把它挂在了button上,同时订阅ButtonClicked这个事件处理器。看下文对ButtonClicked的定义,当事件发生时,它会在文本框(textbox)里面打印这句话。然后再回到Main函数里面,new一个MyForm的对象,然后调用showDialog,弹出窗口。当我们执行这个程序的时候,会出现控制台和这个Form,我们点击Say Hello就会在文本框里面看见Hello World。
回过头来想想,事件的五要素齐了吗?

  • 事件拥有者——button
  • 事件——Click
  • 事件响应者——form
  • 事件处理器——ButtonClicked
  • 事件订阅——+=

很完整,五要素都齐全。这是事件的响应者用自己的方法订阅着自己的字段成员的某个事件的模型,当然你也可以写成其他的方式,留给读者自己钻研吧。
但是其实还有一个问题,为什么事件处理器ButtonClicked要接收两个莫名其妙的参数?其实,旧版本的订阅事件是这样写的:

this.button.Click += new EventHandler(this.ButtonClicked);

把鼠标移到EventHandler上面,你会发现这竟然是一个委托。这说明了什么,我们用到的事件处理器,必须符合事件要求的一套规矩——符合EventHandler的规矩,接收两个参数,一个object类型的参数,一个EventArgs的参数。所以看回ButtonClicked,它要接收那两个参数,是因为委托的要求。但是现在基本不会用旧版本的订阅写法了,尽量都用上面例子的写法。
回忆一下我在C#方法Extra中提到的lambda表达式,在这里能用吗?当然可以,特别是ButtonClicked这种简单的函数,lambda表达式是最喜欢的,使用方法为:

this.button.Click += (sender, e) =>
                {
                    this.textBox.Text = "Hello World!";
                }; 

希望你还记得lambda表达式的语法,这几行代码直接省去了声明和定义ButtonClicked的功夫,省时省力。

自定义一个事件

如果C#提供给你的事件不适合当前的场景,那么我们可以自己定义一个事件,具体如下:

using System;
using System.Threading;

namespace EventDevelop
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();                                     //事件拥有者
            Waiter waiter = new Waiter();                                           //事件响应者

            customer.Order += Waiter.WaiterAction;                                  //订阅事件

            customer.CustomerAction();
            customer.PayTheBill();

            Console.ReadLine();
        }
    }

    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);   //先声明一个委托,作为事件订阅的规定

    public class OrderEventArgs : EventArgs                                        //事件参数,继承自EventArgs,有两个属性
    {
        public string DishName { get; set; }
        public string Size { get; set; }
    }

    public class Customer                                                          //事件源,发送信息的一方
    {
        private OrderEventHandler eventHandler;
         
        public event OrderEventHandler Order                                       //声明事件,用OrderEventHandler约束
        {
            add                                                                    //添加事件处理器
            {
                this.eventHandler += value;                                        //value是传进来的处理器
            }

            remove                                                                 //移除事件处理器
            {
                this.eventHandler -= value;                                        //移除用-=
            }
        }

        public double Bill { get; set; }

        public void PayTheBill()
        {
            Console.WriteLine("I will pay ${0}.", this.Bill);
        }

        public void WalkIn()
        {
            Console.WriteLine("Walk into the restaurant.");
            Thread.Sleep(1000);                                                   //Sleep是让控制台等你一段时间,括号里面是毫秒数,1000毫秒就是1秒
        }

        public void SitDown()
        {
            Console.WriteLine("Sit down.");
            Thread.Sleep(1000);
        }

        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think...");
                Thread.Sleep(1000);
            }

            if (this.eventHandler != null)                                       //调用之前判空,防止抛出异常
            {                                                                    //有事件订阅eventHandler就不为空
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chiken";
                e.Size = "large";

                this.eventHandler.Invoke(this, e);
            }
        }

        public void CustomerAction()
        {
            Console.ReadLine();

            this.WalkIn();
            this.SitDown();
            this.Think();
        }
    }

    public class Waiter
    {
        public static void WaiterAction(Customer sender, OrderEventArgs e)        //事件处理器,根据委托规定,输入两个参数
        {

            Console.WriteLine("I will serve you the dish {0}.", e.DishName);
            Thread.Sleep(1000);

            double price = 10;

            switch (e.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }

            sender.Bill += price;
        }
    }
}

这是完整的事件声明。在声明事件之前,你需要准备一个“规矩”,也就是先声明一个委托,约束事件处理器,同时委托的声明伴随事件源和事件参数的声明,自己声明的OrderEventArgs一般要继承Microsoft给你的EventArgs
应该有人留意到我上次写委托的时候,没有给出声明委托的语法,因为我想事件一定用得到(真不是忘了)。声明委托就在第22行,格式为:访问修饰符+delegate+返回值+名字标识符+参数表,很简单的。
最终输出结果:
事件结果2

总结

事件一般不用自己声明,Microsoft给你的一般都够用了。事件的应用范围真的很广,就像你双击文件夹就会打开,这就是一个事件。事件本事建立在委托的基础上,实现一系列的功能,没有委托就没有事件。我当初说事件处理器是一个回调方法,现在留给读者自己细品一下吧。

后记

没想到事件要写这么复杂= =,有点出乎意料,不过它本身确实挺综合的。接下来复习考试了,1月有空再想想水些别的吧。社团那边好起来了,工作室能拿到是没想到的,再接再厉吧。我月底才更博,就是2077的问题!