`

C#学习笔记——事件

 
阅读更多

 

事件

事件和委托相似

事件的很多方面和委托相似。其实,事件就好像被简化的针对特殊用途的委托。

注册到事件上的方法会在事件触发时被调用。

下面是一些有关事件的重要事项。

触发( raisc)事件:调用(invoke)或触发(fire)事件的术语。当事件被触发时,所有注册到它的方法都会被依次调用。

发布者

订阅者

事件处理程序:注册到时间的方法。可以在事件所在的类或结构中,或者在不同的类或结构中。

 

 

事件有私有委托

    委托和事件的行为之所以相似,是有充分理由的。事件包含了一个私有的委托。有关事件的私有委托需要了解的重要事项如下:

    事件提供了对它的私有控制委托的结构化访问。

    与委托中的其他操作不一样,对于事件我们只可以添加、删除或调用事件处理程序。

事件被触发时,它调用委托来依次调用调用列表中的方法。

 

源代码组件概览

    需要在事件中使用的代码有5部分。

委托类型声明:事件和时间处理程序必须有共同的前面和返回类型,它们通过委托类型声明进行描述。

事件处理程序声明:这些在订阅者类的方法(事件处理程序)中的描述会在事件触发时被执行。它们不需要有独立的方法,它们可以是匿名方法或lambda表达式。

事件声明:这个事件发布者类中的声明保存并调用事件处理程序。

事件注册:这段代码把事件连接到事件处理程序。

触发事件的代码:发布者类中的这段代码调用事件导致它调用事件处理程序。

 

声明事件

发布者类必须提供事件和触发事件的代码

创建事件比较简单。只需要委托类型和名字。

 

例:

事件声明的语法如下代码所示,代码中声明一个叫做Elaspsed的时间。注意如下有关Elaspsed事件的内容:

声明在一个叫做MyTimeClass的类中。

它接受返回类型和签名与EventHandler委托类型匹配的事件处理程序。

它被声明为public,于是其他类和结构可以在这上面注册事件处理程序。

 

class MyTimeClass

{

public event EventHandler Elapsed;

 

}

 

可以通过使用逗号分隔的列表在一个声明语句中声明一个以上的事件。

例:

public event EventHandler MyEvent1, MyEvent2,OtherEvent;

 

我们还可以使用static关键字让事件变成静态的:

public static event EventHandler Elapsed;

 

 

事件是成员

事件不是类型,事件是成员。这点引出几个重要特性。

由于时间不是类型,我们不能使用对象创建表达式(new表达式)来创建它的对象。

由于事件是成员:

它必须声明在类或结构中,和其他成员一样;

我们不能在一段可执行代码中声明事件;

事件成员被隐式自动初始化为null

 

 

委托类型和EventHandler

    事件声明需要委托类型的名字,我们可以声明一个委托类型或使用已存在的。如果我们声明一个委托类型,它必须指定事件保存的方法的签名和返回类型。

    一个更好的方法是,使用.NET BCL使用的并被指定为事件使用标准的预定义委托类型。强烈推荐使用它,那就是EventHandler,它的声明如下代码所示。在本章之后会介绍更多关于委托EventHandler的细节。

public delegate void EventHandler(object sender, EventArgs e);

 

 

触发事件

事件成员本身只是保存了需要被调用的事件处理程序。如果事件没有被触发,什么都不会发生。我们需要确保在合适的时候有代码来做这件事情。

    例如,如下代码触发了Elapsed事件。注意如下有关代码的事项:

        在触发事件之前和null进行比较,从而查看是否包含任何事件处理程序,如果事件是null,则表示没有。

        触发事件本身看起来像调用函数一样。

            使用事件名称,后面跟的参数列表包含在圆括号中。

            参教列表必须匹配事件的委托类型。

if (Elapsed !=null)    //确认有方法可以执行

  Elapsed(source, args);  //抛出异常

   事件名  参数列表

 

 

    把事件声明和触发事件的代码放在一起便有了如下的发布者类声明。这段代码包含了两个成员:事件和一个叫做OnOneSecond的方法,它触发了该事件。

public class MyTimerClass

{

   public event EventHandler Elapsed;    //声明事件

private void OnOneSecond(object source, EventArgs args)

{

if (Elapsed!=null)    //确认有方法可以执行

           Elapsed(source, args);   //发起事件

}

 

//下面的代码确认OnOneSecond方法每1000毫秒被调用一次

}

 

    至此,我们的OnOneSecond方法有点神秘,每1秒会被调用一次。在本章后面,我会演示如何

实现它。但在这里,记住这些要点:

      发布者类有一个作为成员的事件。

      类包含了触发事件的代码。

 

 

 

 

订阅事件

    要为事件添加事件处理程序,处理程序必须有和事件委托一致的返回类型和签名。

    使用+=运算符来为事件增加事件处理程序,如下面代码所示。

    方法可以是下面的任意一个:

      实例方法

      静态方法

      匿名方法

      lambda表达式

 

    例如,下面代码为Elapsed事件增加了三个方法:第一个是使用方法形式的实例方法,第二个是使用方法形式的静态方法,第三个是使用委托形式的实例方法。

        实例方法

mc.Elapsed+=ca.TimerHandlerA;    //方法引用形式

mc.Elapsed+=ClassB.Time:fflandlerB;  //方法引用形式

  事件成员    静态方法

 

mc.Elapsed+=new EventHandler(cc.TimerHandlerC);  //委托形式

 

    和委托一样,我们可以使用匿名方法和lambda表达式来增加事件处理程序。例如,如下代码先使用lambda表达式然后使用了匿名方法。

mc.Elapsed += (source, args) =>

   {

     Console.WriteLine( " Lambda  expression. ");

   };

 

 

mc.Elapsed += delegate object source, EventArgs args)  //匿名方法

{

Console.WriteLine("AnonyNous method.");

}

 

 

如下程序使用了在前面定义的MyTimerClass类。代码执行如下工作:

它从两个不同的类实例注册两个事件处理程序。

在注册事件处理程序后,它休眠2秒。在这段时间内,计时器类会触发两次事件,两个事件处理器每次都会被执行。

 

public class MyTimerClass { … }

class ClassA

{

   public void TimerHandlerA(object obj, EventArgs e)       //事件处理程序

   {

     Console.HriteLine(-Class A handler called');

}

}

 

class ClassB

{

   public static void TirnerHandlerB(object obj,  EventArgs e)    //静态的

   {

      Console.WriteLine("Class B handler called");

   }

}

 

class Program

{

    static void Main()

    {

    ClassA ca = new ClassA();    //创建类对象

    MyTimerClass mc=new MyTimerClass();    //创建计时器对象

mc.Elapsed += ca.TimeHandlerA;        //添加处瑾程序A(实例)

mc.Elapsed += ClassB.TimerHandlerB;        //添加处理程序B(尊态的)

Thread.Sleep(2250);

}

}

这段代码输出:

    Class A handler called

    Class B handler called

    Class A handler called

Class B handler called

 

 

移除事件处理程序

我们可以使用-=运算符从事件移除一个事件处理程序,如下所示。

mc.Elapsed-=ca.TimerHandlerA;    //移除事件处理程序A

 

例如,如下代码在前两次事件被触发之后移除了ClassB的事件处理程序,然后让程序再运行

2秒。

 

...

mc.Elapsed+= ca.TimerHandlerA;    //添加实例事件处理程序A

mc.Elapsed+= ClassB.TimerHandlerB;    //添加静态事件处理程序B

 

ThreadSleep(2250);//休眠2秒以上时间

mc.Elapsed-=ClassB.TimerHandlerB;    //移除静态事件处理程序B

Console.WriteLine(“Class B event handler removed”);

 

Thread.Sleep(2250);    //休眠2秒以上时间

 

 

这段代码产生了如下的输出。前4行是在前2秒内两个处理程序被调用两次的结果。在ClassB的处理程序被移除后,在最后的2秒内只有ClassA的实例处理程序被调用。

 

 

标准事件的用法

    GUI编程是事件驱动的,也就是说在程序运行时,它可以在任何时候被事件打断,比如按钮点击、按下按键或系统定时器。在这些情况发生时,程序需要处理事件然后继续其他事情。

    对于使用C#事件而言,最好的例子是异步处理程序事件。Windows GUI编程如此广泛地使用了事件,对于事件的使用,.NET框架提供了一个强烈推荐遵循的标准模式。

    事件使用的标准模式的根本就是System命名空间声明的EventHandler委托类型。EventHandler委托类型的声明如下面代码所示。

      第一个参数用来保存触发事件的对象的引用。由于是object类型的,所以可以匹配任何类型的实例。

      第一个参数用来保存有关状态对于应用程序来说是否合适的状态信息。

      返回类型是void

public delegate void EventHandler(object sender, EventArgs e);

 

 

 

使用EventArgs

EventHandler委托类型的第二个参数是EventArgs类的对象,它声明在System命名空间中。你可能会想,既然第二个参数用于传递数据,EventArgs类的对象应该可以保存一些类型的数据。你可能错了。

 

EventArgs被设计为不能传递任何数据。它用于不需要传递数据的事件处理程序——通常会被忽略。

如果你希望传递数据,必须声明一个从EventArgs继承的类,使用合适的字段来保存需要传递的数据。

 

尽管EventArgs类实际上并不传递数据,但它是使用EventHandler委托模式的重要一部分。不管参数使用的实际类型是什么,object类和EventArgs总是基类。这样EventHandler就能提供一个对所有事件和事件处理器都通用的签名,只允许2个参数,而不是各自都有不同签名。

 

 

通过扩展EventArgs来传递数据

为了向自己的事件处理程序的第二个参数传入数据,并且又符合标准惯例,我们需要声明一个派生自EventArgs的自定义类,它可以保存我们所需传入的数据。类的名称应该以EventArgs结尾。

例如,如下代码声明了一个自定义类,它能将字符串存储在名称为Message的字段中。

    自定义类名    基类

    public class MyTCEventArgs: EventArgs

    {

    public string Message;    //存储Message

    public MyTCEventArgs(string s)    //构造函数设置message

    {

        Message=s;

}

 

 

使用自定义委托

    既然我们已经有了一个自定义类,可以让我们在事件处理程序的第二个参数中传入数据。我们需要一个委托类型来使用新的自定义类,可以有两种方式这么做。

    第一种方式是使用非泛型委托。实现方式如下:

      使用自定义的类类型创建一个新的自定义委托,如下代码所示。

      在事件代码的其他部分中使用新的委托名称。

                   自定义委托名    自定义类

public delegate void MyTCEventHandler(object sender,MyTCEventArgs e);

 

第二种方式是由C# 2.0引入的,使用EventHandler泛型委托的方式。要使用泛型委托,以如下方式来实现,代码如下所示。

    在方括号中放置自定义类。

    无论希望在哪里使用自定义委托类型的名称,都使用完整的字符串。例如,event声明是这样的:

           使用自定义类的泛型委托

public event EventHandler<MyTCEventArgs> Elapsed;

                                     事件名

 

    在其他4个事件相关的代码段中使用这个自定义类和自定义委托(泛型或非泛型形)。

    例如,如下代码更新了MyTimerClass代码来使用叫做MyTCEventArgs的自定义EventArgs类和泛型EventHandler◇委托。

public class MyTCEventArgs: EventArgs

{

  public string Message;

  public MyTCEventArgs(string s) {         // 自定义类声明

    Message=s;

  }

}

 

class MyTimerClass  //泛型委托

{

public event EventHandler<MyTCEventArgs> Elapsed;   //事件声明

private void OnOneSecond(object obj, EventArgs e)

{

        if (Elapsed !=null){

            MyTCEventArgs mtcea=new MyTCEventArgs('Message from OnOneSecond”);    发起事件的代码

        Elapsed(obj, mtcea);

        }

}

}

 

class ClassA

{

public  void  TimerHandlerA(object obj,MyTCEventArgs e)

{

    Console.WriteLine(“Class A Message{0}”,Message);    //事件处理程序

}

}

 

 

class Program

{

static void Main()

    {

ClassA ca = neti ClassA();

MyTimerClass mc = new MyTimerClass();

    mc.Elapsed+=                                     //注册事件处理程序

        new EventHandler<MyTCEventArgs>(ca.TimerHandlerA);

        Thread.Sleep(3250);

    }

}

    这段代码产生了如下的输出:

    Class A Message:  Message from OnOneSecond

    Class A Message:  Message from OnOneSecond

Class A Message:  Message from OnOneSecond

 

 

 

 

MyTimerClass代码

    既然你已经看过了使用事件需要实现的五个组件的代码,那么我就可以展示一下代码使用的完整的MyTimerClass类。

    关于类的大多数事项都很明白了——它有一个叫做Elspsed的事件可以被订阅,还有一个叫做OnOneSecond的方法每隔1秒会被调用一次并触发事件。剩下的一个问题就是:“什么导致OnOneSecond1秒就被调用一次?”

答案是:OnOneSecond本身就是一个事件处理程序,该事件处理程序订阅了System.Timers命名空间中Timer类的一个事件。Timer的事件每1000毫秒触发一次并调用OnOneSecond事件处理程序,然后它再触发MyTimerClass类中的Elapsed事件。

    Timer类是很有用的工具,因此我会再介绍一点有关它的内容。首先,它有一个叫做Elapsed的公共事件。听上去很熟悉,因为我还用这个名字来命名了MyTimerClass中的事件。除了名字之外没有其他联系了,我也可以为事件取其他任何名字。

 

 

Timer的属性之一是Interval,它是double类型的,并指定了触发事件间隔的毫秒数。代码用到的另外一个属性是Enabled,它是bool类型的,用来开启和停止计时器。

    实际代码如下。我之前唯一没有提到过的就是一个叫做MyPrivateTimer的私有计时器字段和类的构造函数。构造函数用于设置内置计时器并附加OnOneSecond事件处理程序。

public class NlyTimerClass

{

public event EventHandler Elapsed;

private void OnOneSecond(object obj, EventArgs e)

{

if (Elapsed != null)

Elapsed(obj, e);

}

//-----------------------------

private System.Timers.Timer MyPrivateTimer;  //私有计时器

public MyTimerClass()    //构造函数

{

MyPrivateTimer=new System.Timers.Timer();    //创建私有计时器

        //下面的语句将上面的OnOnSecond设置成了

        //类计时器的Elapsed事件的事件处理程序

        //它与我们上面声明的Elapsed事件完全无关

MyPrivateTimer.Elapsed+=OnOneSecond;        //附加事件处理程序

MyPrivateTimer.lnterval =1000;

MyPrivateTirner.Enabled = true;

}

}

 

 

事件访问器

    本章介绍的最后一个主题是事件访问器。之前我提到过,+=-=运算符是事件允许的唯,运算符。看到这里我们应该知道,这些运算符有预定义的行为。

    然而,我们可以修改这些运算符的行为,而且当使用它们时,可以让事件执行任何我们希望的自定义代码。我们可以通过为事件定义事件访问器来实现。

      有两个访问器:addremove

      声明事件的访问器看上去和声明一个属性差不多。

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

 

public event EventHandler Elapsed

{

    add

    {

….  //执行+=运算符的代码

    }

    remove

    {

….   //执行-=运算符的代码

    }

}

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

移除事件注册的方法。

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

 

 

 

 

分享到:
评论

相关推荐

    C#学习笔记——窗体应用程序

    ### C#学习笔记——窗体应用程序 #### 一、引言 本文旨在详细介绍如何使用Visual Studio 2010创建Windows Form应用程序(WinForm),特别适合于初学者。通过本教程,您将学会如何从零开始搭建一个基本的WinForm项目,...

    c#学习笔记——学习心得

    根据提供的标题、描述、标签和部分内容,我们可以详细地解析C#中的关键概念和技术要点。以下是对这些知识点的深入探讨: ### C#中的基本概念 #### 常数(Const) 在C#中,常数使用`const`关键字声明。它们是隐式...

    C#6.0学习笔记——从第一行C#代码到第一个项目设计(源码)

    C#6.0学习笔记——从第一行C#代码到第一个项目设计书籍配套源码。书籍下载地址:https://download.csdn.net/download/wosingren/10463453 https://download.csdn.net/download/wosingren/10463468

    C#6.0学习笔记 从第一行C#代码到第一个项目设计周家安著

    《C# 6.0学习笔记:从第一行C#代码到第一个项目设计》是由周家安编著的一本深入浅出的C#编程教程。这本书主要针对C#初学者和有一定基础的开发者,旨在帮助读者全面理解C# 6.0版本的新特性和编程技巧,并通过实际项目...

    C#6.0学习笔记——从第一行C#代码到第一个项目设计(第二个包)

    在本书最后一章,专门设计了综合实例——“综合实例1: 照片面积计算机器”和“综合实例2: 文件加密与解密工具”,通过这两个综合实例,既可以将前20章的知识融会贯通,又可以抛砖引玉,真正教会读者开发C#应用...

    C#6.0学习笔记——从第一行C#代码到第一个项目设计(第一个包)

    在本书最后一章,专门设计了综合实例——“综合实例1: 照片面积计算机器”和“综合实例2: 文件加密与解密工具”,通过这两个综合实例,既可以将前20章的知识融会贯通,又可以抛砖引玉,真正教会读者开发C#应用...

    C#6.0学习笔记 从第一行C#代码到第一个项目设计 随书源码

    本学习笔记旨在帮助初学者从零基础开始掌握C#编程,逐步进阶到能够独立设计项目。随书源码为学习提供了实践操作的平台,确保理论与实践相结合。 一、C# 6.0基础语法 1. 变量与数据类型:C#支持多种数据类型,包括...

    C#学习笔记之——调用C++

    ### C#调用C++知识点详解 #### 一、引言 随着软件开发技术的不断发展,跨语言编程已经成为一种常见的需求。特别是在企业级应用中,往往需要利用不同语言的优势来构建更加高效、灵活的系统。C#作为一种面向对象的...

    C#学习笔记——基本语法

    C#,又名Csharp,天朝喜欢叫C井。 C#是一种面向对象的编程语言。在面向对象的程序设计方法中,程序有各种相互交互的对象组成。相同种类的对象通常具有相同的类型,或者说,是在先沟通那个的class中。 例如,以...

    编程学习笔记——初级

    编程学习笔记——初级 这篇笔记主要涵盖了初学者在北大青鸟学习编程时涉及的基础知识,包括DOS命令、Java和C#的基础、SQL Server的基本概念以及HTML基础等内容。以下是各个主题的详细说明: 1. DOS命令: DOS是...

    课件 C# 共2——02

    【标签】"C# 共2——02 课件" 明确指出这是与C#编程相关的学习资料,"共2——02"表示这是一个系列课程的第二个部分,而"课件"则提示这些内容可能包括演示文稿、讲义或其他辅助教学材料。 【压缩包子文件的文件名称...

    工作流学习笔记——很好很详细

    工作流学习笔记——很好很详细 工作流(Workflow)是一种自动化业务流程的技术,它将复杂的任务分解成一系列可管理的步骤,并按照预定义的规则和条件执行这些步骤。在.NET框架中,Microsoft提供了Windows Workflow ...

    东北大学秦皇岛分校c#大作业——电子笔记本

    总的来说,"东北大学秦皇岛分校c#大作业——电子笔记本"是一个很好的实践项目,它将理论知识与实际操作相结合,帮助学生在实践中提高编程能力。通过这样的动手练习,相信同学们能够深入理解C#语言,并能独立完成类似...

    广成科技CAN学习笔记——CANopen基础.pdf

    广成科技的这篇《CANopen基础》学习笔记详细介绍了CANopen协议的基本概念、结构、通信机制以及相关的操作技术。以下是对该笔记内容的知识点梳理: 1. CAN通讯状态指示:CANopen协议规定了LED灯的不同闪烁状态表示...

    C#学习笔记

    ### C# 学习笔记 —— 第一天的基础知识 #### 输出与输入操作 - **输出**: 在 C# 中,最基本的输出方法是通过 `Console.WriteLine()` 方法。此方法允许你输出一行文本到控制台。其中 `{index}` 表示格式化输出的...

    c#学习笔记.txt

    c#学习笔记(1) 51099在线学习网发布 文章来源:网络收集 发布时间:2006-05-25 字体: [大 中 小] 51099在线学习网 http://www.51099.com 1, 结构(struct) 与 类(class) [attributes] [modifiers] struct ...

    C#学习笔记网络篇.pdf

    本节讨论了如何实现网络抓包与分析功能,起源于一个项目需求——了解两个计算机之间通过网络传输的具体内容。为实现这一目标,作者研究了免费的类库WinPCap,并决定使用C#重新实现WinPCap的基本接口,命名为`...

    Oracle9i备课笔记——吕海东

    Oracle9i备课笔记——吕海东 第1讲 Oracle9i简介 目的: 1. 了解数据库的发展,关系数据库的基本原理。 2. 了解目前市场上流行的数据库产品及特点 3. 了解Oracle数据库的发展 4. 掌握Oracle9i产品系列 5. 掌握Oracle...

Global site tag (gtag.js) - Google Analytics