你的位置:首页 > ASP.net教程

[ASP.net教程]事件的解密


在前面一篇中写到了委托,也说了委托是C#中很多特性的基础,这篇要讲的事件,就是建立在委托之上的。在C#1.0中,委托和事件是最重要的两个特性。

1、什么是事件?

事件设计到两类角色——事件发布者和事件订阅者。当某个事件发生后,事件发布者会发布消息;事件订阅者会接收到信息,并做出相应的处理,这就是事件的过程。

 

2、使用事件

2.1 定义事件

在C#中定义事件和定义类的成员是很相似的,只要一个event关键字就可以了。比如:

public event EventHandler birthday;

其中event是关键字,而EventHandler是委托类型。

所以可以把事件定义的结构总结为:访问修饰符 event 委托类型 事件名;其中委托类型可以是自定义的委托类型,也可以是.NET类库中预定义的委托类型EventHandler。

2.2 订阅和取消事件

事件订阅者需要订阅事件发布者发布的事件消息,以便在事件被触发式接收消息并做出相应处理。在C#中,可以使用“+=”来订阅事件,使用“-=”来取消订阅事件。

public class Bridegroom
{
  //自定义委托
  public delegate void MarryHandler(string msg);
  //使用自定义委托类型定义事件,事件名为MarryEvent
  public event MarryHandler MarryEvent;

  //发出事件
  public void OnMarriageComing(string msg)
  {
    //判断是否绑定了事件处理方法
    if(MarryEvent!=null)
    {
      //触发事件
      MarryEvent(msg);
    }
  }


  static void Main(string[] msg)
  {
    Bridegroom bridegroom=new Bridegroom();

    //实例化朋友对象
    Friend friend1=new Friend("张三");
    Friend friend2=new Friend("李四");
    Friend friend3=new Friend("王五");

    //使用“+=”来订阅事件
    bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
    bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);

    //发出通知,此时只有订阅了事件的对象才能收到通知
    bridegroom.OnMarriageComing("Friend,I Will Marry!!");
    Console.WriteLine("------------------------------------");

    //使用"-="来取消事件订阅,此时李四将收不到通知
    bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);

    bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);

    bridegroom.OnMarriageComing("Friend,I Will Marry!!");

    Console.ReadKey();
  }
}

  public class Friend
  {
    public string Name;
    public Friend(string name)
    {
      Name=name;
    }
    //事件处理函数,该函数需要符合MarryHandler委托的定义
    public void SendMessage(string message)
    {
      Console.WriteLine(message);
      Console.WriteLine(this.Name+"收到了,到时候准时参加");
    }
  }

值得注意的是,事件处理函数的定义需要与自定义的委托定义保持一致,即参数个数,参数类型和返回类型等需要与委托相同。

除了使用自定义委托类型来定义事件外,还可以使用.NET类库中预定义的委托类型EventHandler来定义事件,需要注意它们的参数。

public class Bridegroom
{
  //使用.NET类库中的类型定义事件,事件名为MarryEvent
  public event EventHandler MarryEvent;

  //发出事件
  public void OnMarriageComing(string msg)
  {
    //判断是否绑定了事件处理方法
    if(MarryEvent!=null)
    {
      Console.WriteLine(msg);
      //触发事件
      MarryEvent(this,new EventArgs());
    }
  }


  static void Main(string[] msg)
  {
    Bridegroom bridegroom=new Bridegroom();

    //实例化朋友对象
    Friend friend1=new Friend("张三");
    Friend friend2=new Friend("李四");
    Friend friend3=new Friend("王五");

    //使用“+=”来订阅事件
    bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
    bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);

    //发出通知,此时只有订阅了事件的对象才能收到通知
    bridegroom.OnMarriageComing("Friend,I Will Marry!!");
    Console.WriteLine("------------------------------------");

    //使用"-="来取消事件订阅,此时李四将收不到通知
    bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);

    bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);

    bridegroom.OnMarriageComing("Friend,I Will Marry!!");

    Console.ReadKey();
  }
}

public class Friend
{
  public string Name;
  public Friend(string name)
  {
    Name=name;
  }
  //事件处理函数,该函数需要符合MarryHandler委托的定义
  public void SendMessage(object s,EventArgs e)
  {
    Console.WriteLine(this.Name+"收到了,到时候准时参加");
  }
}

EventHandler是.NET类库中预定义的委托类型,用于处理不包含事件数据的事件。使用Reflector来查看EventHandler的具体定义:

[Serializable, ComVisible(true), __DynamicallyInvokable]public delegate void EventHandler(object sender, EventArgs e);

从定义中可以看出,该委托类型的返回类型为void,第一个参数sender负责保存触发事件对象的引用,其类型为object;第二个参数e负责保存事件数据。EventArgs类也是.NET类库中定义的类,它不保存任何数据,如果想在事件中包含事件数据,就必须使用EventArgs的派生类来实现。

2.3 扩展EventArgs类

上面说了,如果要在事件中包含事件数据,就必须使用EventArgs的派生类。具体的实现代码如下:

public class MarryEventArgs:EventArgs
{
  public string Message;
  public MarryEventArgs(string msg)
  {
    Message=msg;
  }
}

public class Bridegroom
{
  //自定义委托类型,委托包含两个参数
  public delegate void MarryHandler(object sender,MarryEventArgs e);
  //使用自定义委托类型定义事件,事件名为MarryEvent
  public event MarryHandler MarryEvent;
  //发出事件
  public void OnMarriageComing(string msg)
  {
    //判断是否绑定了事件处理方法
    if(MarryEvent!=null)
    {
      //触发事件
      MarryEvent(this,new MarryEventArgs(msg));
    }
  }


  static void Main(string[] msg)
  {
    Bridegroom bridegroom=new Bridegroom();
    //实例化朋友对象
    Friend friend1=new Friend("张三");
    Friend friend2=new Friend("李四");
    Friend friend3=new Friend("王五");
    //使用“+=”来订阅事件
    bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
    bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
    //发出通知,此时只有订阅了事件的对象才能收到通知
    bridegroom.OnMarriageComing("Friend,I Will Marry!!");
    Console.WriteLine("------------------------------------");
    //使用"-="来取消事件订阅,此时李四将收不到通知
    bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
    bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
    bridegroom.OnMarriageComing("Friend,I Will Marry!!");
    Console.ReadKey();
  }
}

public class Friend
{
  public string Name;
  public Friend(string name)
  {
    Name=name;
  }
  //事件处理函数,该函数需要符合MarryHandler委托的定义
  public void SendMessage(object s,MarryEventArgs e)
  {
    Console.WriteLine(e.Message);
    Console.WriteLine(this.Name+"收到了,到时候准时参加");
  }
}

通过自定义MarryEventArgs事件类扩展了EventArgs类,此时MarryEventArgs带有一个名为Message的事件参数;然后在订阅对象的SendMessage方法中,通过e.Message的方式获得了事件数据,并把事件数据输出。

 

3、事件的本质

从以上的例子我们可以知道,事件是在委托的基础之上的。那么,它们到底有着什么样的关系呢,这个就必须通过Reflector来窥探了。

简单的源代码:

namespace 窥探事件本质
{
  class Program
  {
    public delegate void MarryHanler(string msg);

    public event MarryHanler MarryEvent;
    static void Main(string[] args)
    {
    }
  }
}

Reflector反编译的结果:

图1

 

图2

 

图3

图4

可以看出,C#事件被编译成包含两个公共方法的代码段,一个带有add_前缀,另一个带有remove_前缀,前缀后面是C#事件的名称。

在add_方法中,通过调用了Delegate.Combine()方法来实现的(图3中红框的地方),Delegate.Combine()方法将多个委托组合为了一个多路广播委托。

在remove_方法中,同样采用了Delegate.Remove()方法。

由上面的四张图中可以总结出:

C#的事件是一个特殊的多路广播委托,事件默认含有一个私有的委托类型变量(图2的红框),该变量用于保存对事件处理方法的引用,且该委托类型的变量为私有,只能从定义该事件的类中进行访问。

从反编译的代码中可以看出跟我们学过的属性是相似的。但与事件不同,属性中定义了set访问和get访问器,两个访问器的本质就是以"get_"和"set_"为前缀的两个方法。属性用于对类中的私有字段进行访问,而C#事件也可以看作是“委托字段的属性”,因此可以通过事件来对私有的委托字段进行访问,这也是C#事件特性存在的原因。C#事件机制符合面向对象的封装特性,是代码更安全。