`

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语句。

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics