代码改变世界

浅谈委托和事件之C#的事件模型

2011-07-08 11:43  RyanXiang  阅读(2955)  评论(0编辑  收藏  举报

学习完委托,我们在来看看事件。委托在事件中可谓是物尽其用。

 

一、发布订阅模型                                                                 

      说起事件最经典的范例要属发行订阅范例了,出版社是事件的发布者,订户是事件的订阅者。让我们来二者之间的关系如下图(图片和概念都是网上找的,借花献佛了大家见谅):

事件发行者(Publisher):
  一个事件发行者,也称作发送者(sender),其实就是一个对象,这个对象会自行维护本身的状态信息。当本身状态信息变动时,便触发一个事件,并通知所有的事件订阅者。
事件订阅者(Subscriber)
  对事件感兴趣的对象,也称为接收者(Receiver),可以注册感兴趣的事件,通常需提供一个事件处理程序,在事件发行者触发一个事件后,会自动执行这段代码的内容。

220831623_p

首先订户到出版社订阅图书,出版社发行图书。那么事件在何处呢?出版社发行图书这个动作就是个事件,当出版社发行图书的时候,如果订户有订阅该图书,就会看到订阅的图书。下面我们结合代码来看:

/*
 * Date: 2011-07-07
 * Time: 9:28
 * Create by xiangyun
 */
using System;
 
namespace LearnEvent
{
    //发行者
    public class Publisher
    {
        public delegate void Publish();
        //public  Publish OnPublish; //把event关键字去掉好像也可以
        public event Publish OnPublish;
        public void issue()
        {
            if (OnPublish!=null)
            {
                Console.WriteLine("发行文章");
                OnPublish();//实例化委托后调用
            }
        }
    }
    //订阅者
    public class Subscriber
    {
        public void Receive()
        {
            Console.WriteLine("接受订阅的文章");
        }
    }
    class Program
    {
        public static void Main(string[] args)
        {
            Publisher pb = new Publisher();
            Subscriber sb = new Subscriber();
            
            //注册事件 其实就是把委托实例化后指向具体的执行函数
            pb.OnPublish += new Publisher.Publish(sb.Receive);
          pb.issue();//触发事件
            Console.Write("Press any key to continue . . . ");
          Console.ReadKey(true);
        }
    }
}
1、首先看事件的发行者Publisher类

1)public delegate void Publish();             定义一个委托,至于什么作用我们待会在看。

2)public event Publish OnPublish;            通过event关键字和Publish委托创建事件OnPublish.

3)  public void issue(){……}                   创建事件触发的函数,这里我们要细说一下在该函数中首先判断OnPublish是否为null,如果为null则不会调用OnPublish委托,也就不会触发该事件。如果事件没在Main函数中注册的话,OnPublish就会为null。OnPublish();是什么?我们放到Main函数中去说。

 

2、事件的订阅者Subscriber类。该类很简单只有一个Receive方法,但要注意:该方法一定要和Publisher类里的委托方法签名相同。

 

 

3、Main函数

  36:             Publisher pb = new Publisher();
  37:             Subscriber sb = new Subscriber();
  38:             pb.OnPublish += new Publisher.Publish(sb.Receive);
  39:             pb.issue();

首先创建Publisher、和Subscriber类的实例就不多说了。然后注册OnPublish事件,看起来是不有点眼熟?这不就是我们最开始看的委托么?把sb.Receive方法传递给Publish委托,然后调用pb.issue()方法触发事件。如果没有pb.OnPublish += new Publisher.Publish(sb.Receive);这行代码的话,OnPublish的值就会为null,大家可以试一试,现在大家应该明白OnPublish();到底是什么了吧?其实就是实例化委托后调用的方法。

二、Event关键字                                                      

MSDN上有如下定义event 关键字用于在发行者类中声明事件。

大家可能已经注意到 在发布-订阅模型的代码范例中有这么一句话

public event Publish OnPublish;

如果把event关键字去掉,程序也是可以正常跑通的。这是为什么呢?这是因为事件是委托的一个特别形式,两者都能多路广播。

大家也许会想,既然这样我们为什么还要使用event呢?使用event有几下几点原因:

1、C#提供了event关键字来减轻直接使用委托的负担,编译器会自动提供注册、取消注册的方法和委托必要的成员,从这一点讲,event关键字仅仅是一种语法上的便利措施,只是用来节省键入时间,提高了程序的封装性和易用性。

2、事件应该由事件发布者触发,而不应该由客户端(客户程序)来触发。

关于这部分张子阳的C# 中的委托和事件C#中的委托和事件(续)都有详细的介绍,建议大家必读。

三、符合 .NET Framework 准则的事件

总之使用event事件更合”适宜“,只有定义(public event)事件的对象才可以触发事件。而当你把public event改为简单的委托属性,你会发现从定义它的类外部的其它对象可以随随便触发本不属于自己的delegate,破坏了使用时间最初的目的。

三、符合 .NET Framework 准则的事件                  

以下是符合 .NET Framework 准则事件所必须遵守的规范,摘自张子阳博文:

  • 委托类型的名称都应该以EventHandler结束。
  • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
  • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
  • 继承自EventArgs的类型应该以EventArgs结尾。

下面我们来改写上面的例子时期符合该准则,如下代码:

using System;
 
namespace LearnEvent
{
    
    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string s)
        {
            msg = s;
        }
        private string msg;
        public string Message
        {
            get { return msg; }
        } 
    }
 
    //发行者
    public class Publisher
    {
        public delegate void PublishEventHandler(object sender,CustomEventArgs s);
        public event PublishEventHandler Publish;
        public void issue()
        {
            if (Publish!=null)
            {
                Console.WriteLine("发行文章");
                Publish(this,new CustomEventArgs("事件的自定义数据"));//实例化委托后调用
            }
        }
    }
    //订阅者
    public class Subscriber
    {
        public void Receive(object sender,CustomEventArgs e)
        {
            Publisher pb = (Publisher)sender;//事件发送者
            string messgaeFromPublisher = e.Message;//事件的自定义数据
            Console.WriteLine("接受订阅的文章");
            Console.WriteLine(messgaeFromPublisher);
        }
    }
    class Program
    {
        public static void Main(string[] args)
        {
            Publisher pb = new Publisher();
            Subscriber sb = new Subscriber();
            //注册事件 其实就是把委托实例化后指向具体的执行函数
            pb.Publish += new Publisher.PublishEventHandler(sb.Receive);
            pb.issue();//触发事件
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

 

上述代码,除了增加了CustomEventArgs 类用来发送事件的自定义数据给订户,其它的都和原来的一样,只不过按照规范改写了一下,相信大家能看懂。

 

四、EventHandler和EventHandler委托类

EventHandler 是一个预定义的委托,专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自己的自定义事件数据类型,并且必须要么创建一个委托其中第二个参数的类型为自定义类型(如上述代码所示),要么使用泛型 EventHandler 委托类并用自定义类型替代泛型类型参数。

1、使用EventHandler改写范例,程序无需在定义委托类型,使用预定义委托EventHander即可如下代码:

using System;
 
namespace EventHandlerTest
{
    
    public class Publisher
    {
        public event EventHandler OnPublish;
        public void issue()
        {
            if(OnPublish != null)
            {
                Console.WriteLine("出版读书");
                OnPublish(this,new EventArgs());
            }
        }
    }
    
    public class Subscriber
    {
        public void Receive(object sender,EventArgs e)
        {
            Console.WriteLine("接受订阅的文章");
        }
    }
    
    class Program
    {
        public static void Main(string[] args)
        {
            Publisher pb = new Publisher();           
            Subscriber sb = new Subscriber();
            pb.OnPublish += new EventHandler(sb.Receive);
            pb.issue();
            Console.ReadKey(true);
        }
    }
}

22

2、使用泛型 EventHandler ,如果事件生成事件数据,则无需编写自己的自定义委托代码。参看如下代码:

using System;
 
namespace EventHandlerTest
{
    public class MyEventArgs : EventArgs
    {
        private string msg;
    
        public MyEventArgs( string messageData ) {
            msg = messageData;
        }
        public string Message { 
            get { return msg; } 
            set { msg = value; }
        }
    }
 
    public class Publisher
    {
        public event EventHandler<MyEventArgs> OnPublish;
        public void issue()
        {
            if(OnPublish != null)
            {
                Console.WriteLine("出版读书");
                OnPublish(this,new MyEventArgs("事件自定义数据"));
            }
        }
    }
    
    public class Subscriber
    {
        public void Receive(object sender,MyEventArgs e)
        {
            Console.WriteLine("接受订阅的文章");
            Console.WriteLine(e.Message);
        }
    }
    
    class Program
    {
        public static void Main(string[] args)
        {
            Publisher pb = new Publisher();           
            Subscriber sb = new Subscriber();
            pb.OnPublish += new EventHandler<MyEventArgs>(sb.Receive);
            pb.issue();
            Console.ReadKey(true);
        }
    }
}

五、参考文献                                                                      

http://msdn.microsoft.com/zh-cn/library/w369ty8x(v=VS.80).aspx

http://www.cnblogs.com/jimmyzhang/archive/2007/09/23/903360.html

http://msdn.microsoft.com/zh-cn/library/db0etb8x(v=VS.80).aspx

《C#与.Net 3.0高级程序设计》