.NET学习笔记(六) ——事件

事件其实也是属于类型设计里的内容,不过因为他比较重要,也比较难理解,我就把他单独拿出来学习。

 

1 .下面是一个我们在程序中最常见的代码

 

private void button1_Click(object sender, System.EventArgs e)
 {
          //按妞被点击后执行的操作;    
 }

他的意思是当你点击一个按钮时,他就会执行这个方法里的代码,产生相应的操作。这个时候我们就可以说‘单击’button1这个事件发生的时候,他会自动去执行这个方法。这看起来很简单。点一个按钮,然后执行代码。这在这种基于驱动的程序中很常见。但有个问题不知道大家有没有想过。

这段代码写在那?一般是写在一个from类里,我点这个按钮属于button类。我实际实在button对象里调用了form对象里的方法。那为什么在点按钮的时候他会执行这个方法呢?

说简单点,就是其实就是在一个对象在执行一个方法时调用其他对象的方法。按事件来说就是一个对象的某个事件被触发时,通知其他对象,其他对象执行相应的一些操作。这里就是button的click事件触发后,通知form对象,执行button1_click()方法。那么当事件触发后是如何通知其他对象的呢?

// button1
// 登记方法到button对象
this.button1.Click += new System.EventHandler(this.button1_Click);

在程序中可以看到button1在设置时有这样一句话,他的基本意思就是把button1_click()方法登记到button1.Click对事件象上,这样当触发click事件时就会调用button1_click()方法。也就达到了通知的目的。其中button1.Click又是一个EventHandler类型,这是一个委托类型!什么是委托类型?

 

2.MSDN中对事件和委托的定义

 

关于委托可以看另外一篇介绍委托的  .NET学习笔记(八) ——委托(上)

事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。引发(触发)事件的对象叫做事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。

在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。.NET Framework 定义了一个特殊的类型(Delegate),该类型提供函数指针的功能。

委托是一个可以对方法进行引用的类。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。这样,委托就等效于一个类型安全函数指针或一个回调。虽然委托具有许多其他的用途,但这里只讨论委托的事件处理功能。

 

 

3.事件完整的例子

 

下面是一个完整的例子,一个邮件接受类MailManager,当接受到邮件的时候,就会通知Fax类,Fax就回进行一些操作。2个类型在后面简单的称为A,B。

//A:
class MailManager
{
    //定义MailMsgEventArgs类型
    public class MailMsgEventArgs : EventArgs
    {
        //1.传递给事件接受者类型定义信息
        public readonly String from,to,subject,body;
        
        public MailMsgEventArgs(String from,String to,String subject,String body)
        {
            this.from = from;
            this.to = to;
            this.subject = subject;
            this.body = body;
        }
    }

    //2.下面委托类型定义了接受者必须实现的回调方法原型
    public delegate void MailMsgEventHandler(Object sender,MailMsgEventArgs args);

    //3.事件成员
    public event MailMsgEventHandler MailMsg;

    //4.下面的受保护虚方法负责通知事件的登记对象
    protected virtual void OnMailMsg(MailMsgEventArgs e)
    {
        //有对象登记事件?
        if(MailMsg != null)
        {
            //如果有,则通知委托链表上的所有对象
            MailMsg(this,e);
        }

    }

    //5.下面的方法将输入转化为期望的事件,该方法在新的电子邮件信息到达时被调用
    public void SimulaterArrivingMsg(String from,String to,String subject,String body)
    {
        //构造一个对象保存希望传递给同志接受者的信息
        MailMsgEventArgs e = new MailMsgEventArgs(from,to,subject,body);
        //调用虚方法同志对象事件已发生
        //如果派生类型没有重写该虚方法,对象将通知所有登记事件监听者
        OnMailMsg(e);
    }

}

A这个类型是一个事件的发送方,其中定义了事件成员和其他一些方法。他就相当于前面说到的button类,当这个类型中的事件被触发是,他就通过委托来通知登记的对象。

class Fax
{
    //1:将MailManager对象传递给构造器
    public Fax(MailManager mm)
    {
        //构造一个指向FaxMsg回调方法的MailMsgEventHandler
        //委托实例,然后登记MailManager的MailMsg事件
        mm.MailMsg +=new MailManager.MailMsgEventHandler(FaxMsg);

    }

    //2:MailManager将调用该方法来通知Fax对象收到一个新的电子邮件消息
    private void FaxMsg(Object sender,MailManager.MailMsgEventArgs e)
    {
        //参数sender表示MailManager对象,如果期望和事件的触发者通信,将会用到该参数
        //参数e表示MailManager对象希望提供的一些附加事件信息
        Console.WriteLine("Faxing mail message:");
        Console.WriteLine("From:{0}  To:{1}  Subject:{2}  Body:{3} ",e.from,e.to,e.subject,e.body);
    }
 
       //3:取消登记
    public void Unregister(MailManager mm)
    {
        MailManager.MailMsgEventHandler callback = new MailManager.MailMsgEventHandler(FaxMsg);
        mm.MailMsg -=callback;
    }
}

B这个类型是一个事件接受者,我们看到在内部构造的时候,他已经把自己的方法登记到前面的事件发送者上了。这样在当A中的事件被触发时,B就会接到通知,因为B把自己的方法委托给了A,所以A还会调用B中的方法。

 

 

4.下面是具体对事件模型的讨论

 

事件发送者:

  1. 在类型中定义一个EventArgs类型:用来保存要发送给事件接受者的附加信息。如果没有要传递的信息,可以直接使用系统的EventArgs.Emepty而不用在构造新的EventArgs对象。
  2. 定义一个委托类型:指定事件触发时接受者中被调用的方法的原形。如果我们定义的事件没有需要传递给事件接收者的附加信息,我们便不必定义新的委托类型。直接使用FCL中的System.EventHandler,并将EventArgs.Emepty传递给第2个参数。
  3. 定义一个事件成员:他是EventHandler委托类型。定义他意味着,事件的接受者想要处理这个事件,他们对应必须具有和委托类型相同签名的回调方法。
  4. 定义一个虚方法:当事件发生时调用这个方法,这个方法负责通知登记的接收者对象。该方法接收一个EventArgs参数。这个方法先检查是否有对象登了事件,也就是委托类型的事件成员是不是为空。如果不为空,就触发事件,然后登记的接收者对象的一个方法会被调用。这个方法必须和第2步中定义的委托类型的方法有完全匹配的签名。这个方法是一个虚方法,所以他的继承类可以根据需要重写此方法,使得派生类可以控制事件的触发。通常派生类应该先调用基类的此方法,这样可以使所有已经登记事件的对象都能接受到通知。但是派生类也可以决定不让事件继续传递下去。
  5. 最后还需要定义一个方法,把外部的输入转换为触发事件的动作。当程序调用这个方法时,这个方法内部会构造EventArgs来产生要触发事件时要传递的信息,并调用上面的虚方法来触发事件。从而通知登记的对象,执行接受者中的回调方法。此方法也可以被派生类重写。

事件接受者:

  1. 事件接受者在事件发生时能接受到通知,然后执行自己的一个方法。这个方法必须和事件发送者中委托类型的签名完全匹配,才能在接受到通知时被调用。
  2. 事件接受者想要能接受事件发送者发送来的通知,并在通知后被调用,需要定义一个指向回调方法的委托对象,然后把此对象登记事件发送者中的事件上。
  3. 如果在事件发生时就不想被通知和调用回掉方法,同样也需要定义一个指向回调方法的委托对象,然后把此对象从事件发送者中的事件上注销。

从上面可以看出,事件中最关键的就是发送者和接受者之间的沟通问题。前面说过了,在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。.NET Framework 定义了一个特殊的类型(Delegate),该类型提供函数指针的功能。通过委托进行登记和注销,从而把发送和接受双方联结起来。

 

 

5. 下面看看是怎么通过委托把两者联系起来的

 

当编译上面这句时间成员定义时,会产生3个构造方法

//1:构造一个委托字段
private MailMsgEventHandler MailMsg = null;

//2:向这个委托链表上添加委托对象
private add_MailMsg(MailMsgEventHandler handler)
{
   MailMsg = (MailMsgEventHandler)Delegate.Combine(MailMsg,handler)
}

//3:从这个委托链表上移除委托对象
private remove_MailMsg(MailMsgEventHandler handler)
{
  MailMsg = (MailMsgEventHandler)Delegate.Remove(MailMsg,handler)
}

第一个构造是一个委托类型的字段。这个字段引用的是一个委托链表的首部,链表中包含了那些希望在事件发生时被通知的委托对象。也就是在事件接受者中定义的指向回掉方法的委托对象。一开始这个链表是空的。而后两个构造方法是允许委托对象登记和注销事件的方法。他们通过add_,remove_前缀来命名此方法。调用这些方法时会向链表上添加或移除委托对象。在他们的内部也都是调用System.Delegate的Combine和Remove方法。

在程序中我们见到的却是:

mm.MailMsg +=new MailManager.MailMsgEventHandler(FaxMsg);

mm.MailMsg -=new MailManager.MailMsgEventHandler(FaxMsg);

因为C#编译器对事件提供了内置支持,所以会自动把他转换为上面的add和remove方法。对于那些不支持+=,-=的语言,可以直接调用上面的构造方法。而C#中则要求我们使用这2个操作符,而不允许直接调用方法。

最后要注意的是,只要一个对象仍然登记有另一个对象的事件,该对象就不可能执行垃圾收集。如果我们的类型实现了IDisposable接口的Dispose方法,,我们应该在内部注销其登记的所有事件。

 

 

6 显式的构造委托成员

 

以上演示的是在自定义的类中来定义和使用事件。可以发现,其中最重要的就是通过定义一个委托类型的事件成员把发送和接受者连接起来。上面也谈到了事件成员的3个构造方法。

但在.NET的设计中,控件的事件非常多,如果每个时间都自动生成这3个构造函数,那么会是非常的庞大的代码。这里简单介绍下显式的构造委托成员。

(待继)


如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!
微信打赏 支付宝打赏

1 评论

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注