admin 管理员组

文章数量: 887021

C#事件

目录

一、※初步了解事件

1.事件的定义:

2.事件是一种什么角色:

3.事件使用(事件是干嘛的):

4.原理:

1)5个部分

2)5个动作

5.※※提示:

二、事件的应用

1.实例的一个简单演示

2.事件模型的五个组成部分

1)五个组成部分

2)四种关联模式

三、事件的声明

1.事件的声明

1)完整声明

2)简略声明

2.思考:为什么需要事件

3.事件的本质

4.命名规范

1)事件委托的命名

2)委托参数的命名

3)触发事件的方法的命名:

4)事件的命名约定

四、事件与委托的关系

1.事件真的是以特殊方式声明的委托字段/实例吗?

2.为什么要用委托类型来声明事件

3.对比事件与属性


      ※代表不重要,看看就好,※越多越不重要

一、※初步了解事件

1.事件的定义:

事件英文解释为:a thing that happens,especially something important.

中文通顺的理解为:能够发生的什么事情。

例:手机可以发生响铃这一事情,响铃就是一个事件。

2.事件是一种什么角色:

事件使对象或类具备通知能力的成员。

以手机(对象)举例:手机可以响铃,响铃(事件)使手机具备了通知能力。

3.事件使用(事件是干嘛的):

用于对象或类间的动作协调与信息传递。

4.原理:

事件模型中的两个5

1)5个部分

“发生→响应”中的五个部分:闹钟响铃了小明起床。

其中4个部分很容易看出:闹钟,响铃,小明,起床

其中还有一个隐含的部分为:订阅     

也就是小明订阅了他手机的铃声,手机响铃时,小明才会发生反应。如果是别人的手机,小明没有订阅,就算响铃,小明也不会有反应。

2)5个动作

“发生→响应”中的五个动作:①我有一个事件→②一个人或一群人关心我这个事件→③我的这个事件发生了→④关心这个事件的人会被依次通知到→⑤被通知到的人根据拿到的事件信息(事件参数)对事件进行响应(处理事件)。

5.※※提示:

事件多用于桌面、手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的。

各种编程语言对这个机制的实现方法不尽相同。

Java语言里没有事件这种成员,也没有委托这种数据类型。Java的事件是用接口来实现的。

MVC、MVP、MVVM等模式,是事件模式更高级、更有效的“玩法”。

日常开发的时候,使用已有事件的机会比较多,自己声明的机会比较少,所以先学怎么使用。

二、事件的应用

1.实例的一个简单演示

+= 就是订阅事件的操作符,用法看下列代码

ussng System;
using System.Timers;//需要引入命名空间namespace 事件_简单实例
{class Program{static void Main(string[] args){Timer timer = new Timer();timer.Interval = 1000;//设置时间间隔Boy boy = new Boy();//Elapsed 就是每间隔Interval时间,就触发一次事件timer.Elapsed += boy.Action;//用boy订阅timer的Elapsed事件,处理事件为Action方法timer.Start();Console.ReadLine();}}class Boy{internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine("Jump");}}
}

 也可以多个对象订阅同一个事件,对上面代码稍加改动,如下:

using System;
using System.Timers;namespace 事件_简单实例
{class Program{static void Main(string[] args){Timer timer = new Timer();timer.Interval = 1000;//设置时间间隔Boy boy = new Boy();timer.Elapsed += boy.Action;Girl girl = new Girl();timer.Elapsed += girl.GirlAction;timer.Start();Console.ReadLine();}}class Boy{internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine("Jump");}}class Girl{internal void GirlAction(object sender, ElapsedEventArgs e){Console.WriteLine("Sing!");}}
}

2.事件模型的五个组成部分

1)五个组成部分

①事件的拥有者(event source,对象)

②事件成员(event,成员)

③事件的响应者(event subscriber,对象)

④事件处理器(event handler,成员)

⑤事件订阅----把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”

2)四种关联模式

这五个部分又存在4种关联模式:

第一种(⭐):

 本图的意思是:事件的拥有者和事件的响应者是两个完全不同的对象。代码示例如下:

代码目的:当点击窗体时,Text会显示出当前的时间。

using System;
using System.Windows.Forms;namespace 事件_1
{static class Program{static void Main(){Form form = new Form();//事件的拥有者Controller controller = new Controller(form);//事件的响应者form.ShowDialog();//为了让窗口停下}}class Controller{private Form form;public Controller(Form form){if(form != null){this.form = form;this.form.Click += this.FromClicked;//订阅}}//事件处理器private void FromClicked(object sender, EventArgs e){this.form.Text = DateTime.Now.ToString();}}
}

注意:1.写本段代码时若创建的控制台项目,需要先添加对System.Windows.Form类库的引用

我是直接创建Windows窗体应用项目,然后把主方法里的代码删掉就可以。

如下所示:

  2.VS在写这两段代码时,先写第一段,然后FromClicked下面会出现红色波浪线,然后同时按住Alt+回车键,它会提示你生成第二段的代码。

                this.form.Click += this.FromClicked;
        private void FromClicked(object sender, EventArgs e){this.form.Text = DateTime.Now.ToString();}

第二种(⭐⭐):

图解:事件的拥有者和事件的响应者是同一个对象。 

代码目的和第一种一样。但是由于这个对象需要有事件处理器,而Form这个类是微软给出的,无法对其修改,所以这里可以采用继承。

继承:可以简单理解为MyForm里拥有Form里的所有代码,只不过没有展现出来,然后又在这基础上再写代码。 (继承还有其它东西,可以转移阵地学习)

using System;
using System.Windows.Forms;namespace 事件_2
{static class Program{static void Main(){MyForm form = new MyForm();form.Click += form.FormClicked;form.ShowDialog();}}class MyForm : Form   //继承{internal void FormClicked(object sender, EventArgs e){this.Text = DateTime.Now.ToString();}}
}

注意事项:看第一种的注意。

第三种(⭐⭐⭐):

 图解:事件的响应者是一个对象,事件的拥有者是事件响应者这个对象里的一个字段成员。

代码目的:当点击按钮时,textbox里会显示一串字符。

using System;
using System.Windows.Forms;namespace 事件_3
{static class Program{static void Main(){MyForm myForm = new MyForm();myForm.ShowDialog();}}class MyForm:Form{private TextBox textBox;private Button button;public MyForm(){this.textBox = new TextBox();this.button = new Button();this.Controls.Add(this.button);//把button添加到窗口上this.Controls.Add(this.textBox);this.button.Click += this.ButtonClicked;this.button.Text = "Sey Hellow";this.button.Top = 100;//button与窗口最上边的距离}private void ButtonClicked(object sender, EventArgs e){this.textBox.Text = "Hellow,World!";}}
}

注意事项:看第一种的注意。

第四种:

 没用,就不做代码演示了。

三、事件的声明

1.事件的声明

1)完整声明

用代码来模拟顾客点菜。顾客点菜,服务员处理点菜这个事件。

using System;
using System.Threading;namespace 事件_完整声明
{class Program{static void Main(string[] args){Customer customer = new Customer();Waiter waiter = new Waiter();customer.Order += waiter.Action;customer.Action();//触发这个事件customer.PayTheBill();}}//事件信息,命名规范:事件名+EventArgs//事件信息这个类必须继承自EventArgs,记住就行public class OrderEventArgs:EventArgs{public string DishName { get; set; }public string Size { get; set; }}//委托用来声明事件时//命名规范:事件名+EventHandlerpublic delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer{public OrderEventHandler orderEventHandler;//声明事件,event是声明事件关键字public event OrderEventHandler Order{add{this.orderEventHandler = value;}remove{this.orderEventHandler = value;}}public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.",this.Bill);}//下面几个方法用来触发事件public void Walkln()//进店{Console.WriteLine("Walk into the restaurant.");}public void SitDown()//坐下{Console.WriteLine("Sit down");}public void Think()//思考吃什么{for (int i = 0; i < 5; i++){Console.WriteLine("Let me think...");Thread.Sleep(1000);}if(orderEventHandler!= null){OrderEventArgs e = new OrderEventArgs();e.DishName = "Kongpao Chicken";e.Size = "large";//触发事件处理器this.orderEventHandler.Invoke(this, e);}}public void Action(){Console.ReadLine();this.Walkln();this.SitDown();this.Think();}}public class Waiter{internal void Action(Customer customer, OrderEventArgs e){Console.WriteLine("I will serve you the dish {0}",e.DishName);double price = 10;switch(e.Size){case "small":price *= 0.5;break;case "large":price *= 1.5;break;default:break;}customer.Bill += price;}}
}

注意:和委托有关的类,比如事件拥有者,响应者,事件信息,都应该和委托的访问权限相同,也就是public。

从这个例子中,就可以看出来事件是基于委托的。

事件是基于委托的:1.事件需要用委托类型来做一个约束,这个约束既规定了事件能发送什么消息给响应者,也规定了响应者能收到什么样的事件消息,这就决定了事件响应者的事件处理器必须能够给这个约束匹配上,才能够订阅这个事件。
2.当事件的响应者给事件的拥有者提供了能够匹配这个事件的事件处理器之后,得找个地方把事件处理器保存或者记录下来,能够记录或者引用方法的任务也只有委托类型的实例能够做到。

2)简略声明

像字段一样的声明格式,不是字段

学习简略声明之前,先看一个东西(用的完整声明的代码):

  事件触发这段代码中,把orderEventHandler(委托)换成Order(事件)后,会报这样一个错误:

这说明,事件的右边只能是+=或者-=。

下面开始写简略声明的代码(在完整声明的基础上修改): 

只需要修改两处即可:

1.

修改为 

        public event OrderEventHandler Order;//简略声明

2.

 这里的orderEventHandler修改为Order。

看到这你就会发现,事件后面不是只能加+=或者-=?为啥这里就能直接改,这就是微软设计的这个语法糖带来的影响。注意一下就行。

语法糖:简单点来说就是微软经过一系列操作,让语法写起来更简单。

2.思考:为什么需要事件

思考:有了委托字段,为什么还需要事件?

答:为了程序的逻辑更加“有道理”,更加安全,谨防“借刀杀人”。

怎么理解呢,下面来解释一下:

        public event OrderEventHandler Order;//简略声明

当你把这行代码中的event删掉后,你会发现,程序还是能运行,而且运行结果一模一样。这时候,这行代码就是一个委托字段,那么为什么还需要事件。

下面我把主方法修改一下,写出下列代码:

using System;
using System.Threading;namespace 事件_简略声明
{class Program{static void Main(string[] args){Customer customer = new Customer();Waiter waiter = new Waiter();customer.Order += waiter.Action;OrderEventArgs e1 = new OrderEventArgs();e1.DishName = "Manhanquanxi";e1.Size = "large";OrderEventArgs e2 = new OrderEventArgs();e2.DishName = "Beer";e2.Size = "large";Customer badGuy = new Customer();badGuy.Order += waiter.Action;badGuy.Order.Invoke(customer, e1);badGuy.Order.Invoke(customer, e2);customer.PayTheBill();}}public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer{public OrderEventHandler Order;//简略声明public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.", this.Bill);}//下面几个方法用来触发事件public void Walkln(){Console.WriteLine("Walk into the restaurant.");}public void SitDown(){Console.WriteLine("Sit down");}public void Think(){for (int i = 0; i < 5; i++){Console.WriteLine("Let me think...");Thread.Sleep(1000);}if (Order != null){OrderEventArgs e = new OrderEventArgs();e.DishName = "Kongpao Chicken";e.Size = "large";//触发事件处理器this.Order.Invoke(this, e);}}public void Action(){Console.ReadLine();this.Walkln();this.SitDown();this.Think();}}public class Waiter{internal void Action(Customer customer, OrderEventArgs e){Console.WriteLine("I will serve you the dish {0}", e.DishName);double price = 10;switch (e.Size){case "small":price *= 0.5;break;case "large":price *= 1.5;break;default:break;}customer.Bill += price;}}
}

运行后:

 

 你会发现,badGuy点的菜算在了customer的头上,这就是“借刀杀人”。有了事件之后,把某个对象的某个事件和某个对象的某个事件处理器捆绑起来,只有你这个点菜事件触发时,相对应事件处理器才会触发,价钱才会算在自己头上。

3.事件的本质

所以事件的本质时委托字段的一个包装器:

这个包装器对委托字段的访问起限制作用。

封装的一个重要功能就是隐藏。

事件对外隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能。(就是只能+=,-=)

添加/移除事件处理器的时候可以直接使用方法名,这是委托实例不具备的功能。

4.命名规范

1)事件委托的命名

 除非是一个非常通用的事件约束,例如:微软帮我们声明了一个委托EventHandler.

其实,上面这一段委托没必要声明,因为微软给了一个EventHandler。

先看一下EventHandler的定义:

 object是所有类的基类,所以第一个参数可以接收任何类型的参数。(不懂的先学一下继承)

而我们声明的事件信息那个类,派生于EventArges,所以声明的事件信息那个类型可以传递进来。

下面开始写代码:

第一处修改的地方:

 第二处修改的地方:

 修改为:

    public class Waiter{internal void Action(object sender, EventArgs e){Customer customer = sender as Customer;OrderEventArgs orderInfo = e as OrderEventArgs;Console.WriteLine("I will serve you the dish {0}", orderInfo.DishName);double price = 10;switch (orderInfo.Size){case "small":price *= 0.5;break;case "large":price *= 1.5;break;default:break;}customer.Bill += price;}}

改完后整体的代码:

using System;
using System.Threading;namespace 事件_简略声明
{class Program{static void Main(string[] args){Customer customer = new Customer();Waiter waiter = new Waiter();customer.Order += waiter.Action;customer.Action();customer.PayTheBill();}}public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}public class Customer{public event EventHandler Order;public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.", this.Bill);}public void Walkln(){Console.WriteLine("Walk into the restaurant.");}public void SitDown(){Console.WriteLine("Sit down");}public void Think(){for (int i = 0; i < 5; i++){Console.WriteLine("Let me think...");Thread.Sleep(1000);}if (Order != null){OrderEventArgs e = new OrderEventArgs();e.DishName = "Kongpao Chicken";e.Size = "large";this.Order.Invoke(this, e);}}public void Action(){Console.ReadLine();this.Walkln();this.SitDown();this.Think();}}public class Waiter{internal void Action(object sender, EventArgs e){Customer customer = sender as Customer;OrderEventArgs orderInfo = e as OrderEventArgs;Console.WriteLine("I will serve you the dish {0}", orderInfo.DishName);double price = 10;switch (orderInfo.Size){case "small":price *= 0.5;break;case "large":price *= 1.5;break;default:break;}customer.Bill += price;}}
}

这样,我们就省去了自己来声明委托类型,直接用厂商给出的就行,所以大家还需要对EventHandler进行进一步学习。当然直接声明委托也没毛病。

2)委托参数的命名

委托的参数一般有两个:第一个是object类型,名字为sender(上面那个例子就用到了),实际上就是事件的拥有者。第二个是EventArgs的派生类,参数名一般都为e,也就是最前面说的事件参数。

3)触发事件的方法的命名:

触发事件的方法的命名一般命名为On+事件名。

这个方法的访问级别为protected,不能为public,所以要对以前的代码进行修改,而且还要遵循一个方法干一件事儿的编程思想!

对下面这段代码修改:

 修改为下面这段代码就可以了

        public void Think()//思考吃什么{for (int i = 0; i < 5; i++){Console.WriteLine("Let me think...");Thread.Sleep(1000);}//if (Order != null)//{//    OrderEventArgs e = new OrderEventArgs();//    e.DishName = "Kongpao Chicken";//    e.Size = "large";//    //触发事件处理器//    this.Order.Invoke(this, e);//}OnOrder("Kongpao chicken", "large");}protected void OnOrder(string dishName,string size){if (Order != null){OrderEventArgs e = new OrderEventArgs();e.DishName = dishName;e.Size = size;//触发事件处理器this.Order.Invoke(this, e);}}

4)事件的命名约定

带有时态的动词或者动词短语。

事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用过去时。

※例:closing和closed,一个是关闭中,一个是关闭后,关闭中这个事件发生时,事件处理器可能时提示用户是否要保存文件,关闭后这个事件发生时,事件处理器可能要记录一下用户用这个软件用了多长时间等等。

四、事件与委托的关系

1.事件真的是以特殊方式声明的委托字段/实例吗?

答案是否定的,只是声明的时候看起来像,事件只是简化声明了而已。

为什么会造成这一错觉,是因为1.事件声明的时候用了委托类型,简化声明时与委托字段很相似,很多人就把event看做成了一个像public一样修饰符,其实event就是一个声明事件的关键字。

2.订阅事件的时候+=操作符后面可以是一个委托委托实例(技术比较老),这与委托实例的赋值的方法相同,所以也容易误解。

用一段代码解释一下第二点:

事件的订阅在很久以前用的是下面这种方法,是不是和委托实例赋值的方法相同。

            customer.Order += new EventHandler(waiter.Action);

2.为什么要用委托类型来声明事件

站在source的角度来看,是为了表明source能对外传递哪些消息。

站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样的签名方法来处理(响应)事件。

委托类型的实例将用于储存(引用)事件处理器。

3.对比事件与属性

属性不是字段,很多时候属性是字段的包装器,这个包装器来保护字段不被滥用。

事件不是委托字段,事件是委托字段的包装器,这个包装器用来保护委托字段不被滥用。

包装器永远都不可能是被包装的东西。

本文标签: C事件