正文

.Net 机制下委托(一) 事件委托2007-06-15 10:22:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/Csharpsky/26805.html

分享到:

一、 认识委托
    一个简单的例子:
 
    张三看到餐桌上有一个桔子,由于自己怕动(主要是出于自己要玩游戏,走不开),立刻就对着他妈妈喊:“我要吃桔子,妈妈帮我拿过来。”,接着,他妈妈听到乖儿子要吃桔子,就立刻送去给儿子了。
    从某种意义上来说,把儿子发出消息要桔子的动作与妈妈送桔子给儿子的动作相关联的过程就称为委托,也就是说儿子发出消息要桔子这个事件委派妈妈根据他的消息内容去完成他想要做的事。
 
二、 理解.net机制下的委托概念
   
    关于委托的声明:
   
    delegate 返回类型 方法声明( 参数1,参数2,参数3… );
   
    例如:delegate void ButtonTextChangeEvent( object sender, EventArgs e );
 
    注意:声明一个ButtonTextChangeEvent,就是创建了一个“委托类”,在编译期间,就会产生个委托类在代码区,因而它可以在类里面声明,也可以跟类在同一域(即命名空间中定义),享受与类同等待遇,那么在这个类里面会有一个最关键的函数就是
 
    virtual 返回类型 方法声明(参数1,参数2,参数3…);

图示:
 

    那么这个Ivoke虚函数是用做干什么的呢?它主要是用来调用某个绑定了的函数,说白了,delegate充其量就是一个中间件(相当于我们所见的媒婆)。
 
namespace ConsoleApplicationTest
{
//声明一个委托(如果您学过C++那么它很像C++里面的typedef.)
    public delegate int FunPtr();
    public class A
    {
        //定义一个委托类型的变量(类型:引用)
        public FunPtr ptr;
}
public class Controller
{
    public void AdelegateTest()
    {
         A a = new A();
         a.Ptr = new FunPtr(Fun); //对a实例中的ptr进行委托绑定
    }
    public int Fun(){}
}
}

    声明的委托它是类类型(请记住),定义一个委托类型的变量,主要是为了指向某个静态函数的首地址或是实例的中某个函数首地址。,
    委托是面向对象的、类型安全的和保险的,它是对对象的引用以及对该对象内一个或多个方法的引用组成(即多路广播),那么何为多路广播呢?多路广播就是它可以把委托叠加到一起,形成一个委托链表的形式,进行依次调用。
 
    形式:对象.委托变量 += new 委托类( 委托类中invoke的函数声明形式的函数式 )
    举例:button. ptr += new FunPtr (Fun);
 
    关键是“ += ” 形成了一个委托链 ,也可以把它理解成是向委托数据表中添加函数地址,调用时按委托数据表中的顺序依次转至函数式地址进行执行(顺序:先进先出),当然您也可以用“ -= ”来对委托数据表中的函数地址进行移除操作。
 
    **.委托在 .net下面,虽然类似于C++下面的函数指针,但它比C++更安全,更有效,更容易使用。

三、事件委托

    好,现在我们对委托有一定的了解了,现在开始事件委托的历程。
    简单的说,事件委托就是在对象内进行事件声明,而在该对象之外用事件处理函数与对象声明的事件进行绑定,然后,该对象事件的引发,继而执行相应的事件处理函数的一个过程,例如:Button的OnClick事件。
    好,既然说到Button的事件,那我们就一起来模仿一个Button类及Button值改变后引发事件并执行自定义的ButtonTextChangeEvent事件处理函数的过程,从而以事件数据得到该Button赋值过多少次。
我们先来看看这个过程是如何传递的:
 
传递过程:
     Button –> 赋值 –> 引发ButtonTextChangeEvent –> 调用相应的处理程序
 
    好,现在这流程已经有了,那么,我们一步步来做:
 
namespace ConsoleApplicationTest
{
  public class Button
  {
      private string _Text;
      public string Text
      {
          get { return _Text; }
          set { _Text = value; }
      }
  }
}
  
     到目前为止,一个基本的Button类设计完成(这个就不解释了,谁都知道是啥意思。),它已经具有了赋值的功能,接下来,我们来看第三步,既然要引发事件,那么我们必须得有事件的声明,让事件处理委派于外部处理函数(事件绑定),当对Button.Text赋值时进行引发事件.


namespace ConsoleApplicationTest
{
    public delegate void ButtonTextChangeEventHander( object sender , System.EventArgs e ); //事件委托的声明。
    public class Button
    {
        public event ButtonTextChangeEventHander TxtChange; //定义一个事件委托的引用,以备外部进行事件的绑定.
 
        private string _Text;
        public string Text
        {
            get { return _Text; }
            set { _Text = value; }
        }
    }
}
 
    好,现在事件的声明与Button的事件的引用也定义了,为了达到Button.Text赋值时引发事件,进行那么现在就到需要在set{…}里面进行事件调用的代码编写:

namespace ConsoleApplicationTest
{
    public delegate void ButtonTextChangeEventHander ( object sender , System.EventArgs e ); //事件委托的声明。
 
    public class Button
    {
        public event ButtonTextChangeEventHander TxtChange; //定义一个事件委托的引用,以备外部进行事件的绑定.
        private string _Text;
        public string Text
        {
            get { return _Text; }
            set
            {
                _Text = value;
 
                System.EventArgs e = new EventArgs(); //这里是事件的基类实例.
                
                ChangeTxt(e); //调用事件处理函数.
            }
        }
        private void ChangeTxt(System.EventArgs e)
        {
            if( TxtChange != null )
                TxtChange(this,e);//真正调用外部指派的处理函数.
        }
    }
}
到此为止,可能有人会问,为什么要这样定义:
 
delegate ButtonTextChangeEventHander ( object sender , System.EventArgs e )
 
如果不用,那不也一样可以吗?
原因也很简单,为了符合.net framework CLS的约定,所有的事件都得遵守 M S 的规定,要不然,嘿嘿,你就麻烦了,事件得不到统一的、规范的处理,所以既然用了MS.Net就遵守她的标准吧,当然在上面的那段程序中不用这样麻烦的定义也是可行的。
 
例如:    delegate ButtonTextChangeEventHander (); 或者
          delegate ButtonTextChangeEventHander ( int changeCount ); //加上事件数据
 
     ButtonTextChangeEventHander( object sender , System.EventArgs e );//它应该是你经常看到的吧,这种形式就是MS的一种规定,事件处理函数的一种规定,如果您喜欢,可以再加上N个参数在里面也是可以的,比如:异步回调,但这样做唯的一缺点就是不能与MS的事件相统一符合CLS的规定。
 
    Sender :引发事件的对象 ; e:事件所带的数据
 
需要指出的是:


            if( TxtChange != null )
                TxtChange(this,e);

    如果不加 if( TxtChange != null ) 如果TxtChange并没有绑定事件处理函数,那么它将会发“未将对象引用到实例”的Exception。
 
到目前为止,一个无事件数据的Button设计成型了,那么,现在我们得加上一些绑定代码,进行事件的绑定,得让Button执行赋值时,调用相应的处理函数。
 
using System;
using System.Threading;

namespace ConsoleApplicationTest
{
    public delegate void ButtonTextChangeEventHander( object sender , System.EventArgs e ); //事件委托的声明。
   
    public class Button
    {
        public event ButtonTextChangeEventHander TxtChange; //定义一个事件委托的引用,以备外部进行事件的绑定.
        private string _Text;
        public string Text
        {
            get { return _Text; }
            set
            { 
                _Text = value; 
                System.EventArgs e = new EventArgs(); //这里是事件的基类实例.
                ChangeTxt(e); //调用事件处理函数.
            }
        }
 
        private void ChangeTxt(System.EventArgs e)
        {
            if( TxtChange != null )
                TxtChange(this,e);//真正调用外部指派的处理函数.
        }
    }
 
    /**//**//**//// <summary>
    /// 事件处理类
    /// </summary>
    public class DelegateDemo
    {
        //定义一个实例
        private Button button; 
        public DelegateDemo()
        {
            InitializeComponent();
            //构造函数调用时,就开始操作button进行赋值。
            SetValues();
        }
        public void InitializeComponent()
        {
            button = new Button();
            //对button对象的TxtChange事件引用,进行事件与处理函数的绑定。
            //前面已经说过了,EventHandler是一个类,所以要用new进行创建它的实例,其主要是把button_TxtChange事件处理函数的地址传至这个委托实例。
            button.TxtChange += new ButtonTextChangeEventHander(button_TxtChange); 
        }
        
        public void SetValues()
        {
            string[] values = {"AAA","BBB","CCC","DDD","EEE"};
            for( int i = 0; i < 5; i++ )
            {
                //主要是让程序有个阻塞输出,可以停留一会儿,让你一次看个够J,对理解委托没什么意义。
                Thread.Sleep(2000); 
                //进行赋值,从而引发事件而直接调用button_TxtChange事件处理函数。
                button.Text = values[i]; 
            }
        }
 
        private void button_TxtChange(object sender, System.EventArgs e)
        {
            Console.WriteLine( " Button实例新值。其值为:" + ((Button)sender).Text ); 
        }
    }
    public class MyMain
    {  
        public static void Main()
        {
            DelegateDemo delegateDemo = new DelegateDemo();
            Console.Read();
        }
    }   
}
 
其执行结果:
Button实例新值。其值为:AAA
Button实例新值。其值为:BBB
Button实例新值。其值为:CCC
Button实例新值。其值为:DDD
Button实例新值。其值为:EEE
 
     先总结一下,上面说的都是对于无事件数据的情况,但如果你需要有事件数据了,怎么办?比如:我们大家都比较熟悉的DataGrid它有DataGridCommandEventArgs事件,并在事件中带有CommandArgument,Item,CommandName等事件数据,使得我们可以操作更方便。那么我们这个Button模拟对象中也要有类似的功能怎么办?那就必须实现自己的EventArgs事件类,下面我们一起来实现我们这个Button的EventArgs类。
 
先休息一下,偶到现在还没睡呢,一下就到早上5:30了,幸好是星期六,不上班J.
 
喝了口水,再来,继续。。。
 
在上面的例子,我们要加一个事件数据,用于将Button的赋值次数做为事件数据传给事件处理函数,因此我们自已定义一个EventArgs类来实现。
    /**//// <summary>
    ///事件数据对象
    /// </summary>
    public class ButtonTextChangeEvent : System.EventArgs //继承系统的事件基类
    {
        private readonly int _Num;
       
        public ButtonTextChangeEvent( int num )
        {
            _Num = num;
        }
       
        public int Num
        {
            get{ return _Num; } //赋值次数(主要是让事件处理函数进行调用)
        }
    }
上面的这个类,就是一个包含事件数据的类,继承了系统的EventArgs,添加了一个Num属性,即可从Button中传递赋值次数至事件数据类中,从而让事件处理类进行调用。
 
当然,委托也得稍改动一下参数的声明:
 
     public delegate void ButtonTextChangeEventHander( object sender , ButtonTextChangeEvent e );
 
然后改动相应的委托处理函数的声明等。具体请看完整的实现:
 
using System;
using System.Threading;
namespace ConsoleApplicationTest
{
    /**//// <summary>
    /// 包含事件数据的类
    /// </summary>
    public class ButtonTextChangeEvent : System.EventArgs
    {
        private readonly int _Num;
        public ButtonTextChangeEvent( int num )
        {
            _Num = num;
        }
        public int Num
        {
            get{ return _Num; }
        }
    }
    public delegate void ButtonTextChangeEventHander( object sender , ButtonTextChangeEvent e );
    /**//// <summary>
    /// 对象模型
    /// </summary>
    public class Button
    {
        public event ButtonTextChangeEventHander TxtChange;
       
        private int _setValueCount;
        private string _Text;
        private void ChangeTxt(ButtonTextChangeEvent e)
        {
            if( TxtChange != null )
                TxtChange(this,e);
        }
        public string Text
        {
            get
            {
                return _Text;
            }
            set
            {
                _Text = value;
                _setValueCount += 1;
                ButtonTextChangeEvent e = new ButtonTextChangeEvent(_setValueCount);
                ChangeTxt(e);
            }
        }
    }
    /**//// <summary>
    /// 实例类
    /// </summary>
    public class DelegateDemo
    {
        private Button button;
        public DelegateDemo()
        {
            InitializeComponent();
            SetValues();
        }
        public void InitializeComponent()
        {
            button = new Button();
            button.TxtChange += new ButtonTextChangeEventHander(button_TxtChange);//是不是感觉很像我们在实际中使用的那样,嘿嘿。
        }
        public void SetValues()
        {
            string[] values = {"AAA","BBB","CCC","DDD","EEE"};
 
            for( int i = 0; i < 5; i++ )
            {
                Thread.Sleep(2000);
                button.Text = values[i];
            }
        }
        private void button_TxtChange(object sender, ButtonTextChangeEvent e)//这里也很像
        {
           Console.WriteLine("第"+e.Num.ToString()+"次重新设定Button实例新值。其值为:" + ((Button)sender).Text );   
   }
    }
    public class MyMain
    { 
        public static void Main()
        {
            DelegateDemo delegateDemo = new DelegateDemo();
            Console.Read();
        }
    }  
}
 

    好,现在我来把上面的这个例子主要流程说一下:
 
1.   对象模型,主要是为了完了一个Button的设计,并加入事件引用的定义,以备外部其它类对象进行事件的绑定。
2.   ButtonTextChangeEvent 它就是一个包含事件数据的一个类,主要是为了可以让外部事件处理函数进行调用。
3.   实例类,它里面的InitializeComponent()函数就实现了Button中TxtChange事件委派到具体的哪一个处理函数,这个函数,可以把它看成一个媒婆,专做三八事儿的J.
4.   然后,直接在构函数中,执行SetValues()函数,对button进行赋值,从而引发ButtonTextChangeEvent。

阅读(5013) | 评论(1)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册