zoukankan      html  css  js  c++  java
  • 事件与委托理解2

    当我设法学习事件与委托时,我阅读了很多文章去完全地理解它们并使用它们,现在我想把我学到的展现在这里,其中有很多知识你也需要学习。

    什么是委托?

    委托和事件是紧紧联系在一起的。委托是函数(方法)指针,更确切地说,委托保持方法的引用。

    委托是一个类。当你创建它的实例的时候,你传递将被委托调用的方法名(做为委托构造器的参数)。

    每个委托都有一个特征。例如: Delegate int SomeDelegate(string s, bool b); 是一个委托声明。

    我之所以说它有一个特征,是因为它都返回一个int类型的值并带有两个参数,类型分别为 string和bool。 我说过,当你实例化委托时,你传递将被委托调用的方法名做为它的构造器参数。重要的一点是只有与委托具有相同特征的方法才能做为其参数。

    看看下面的方法: private int SomeFunction(string str, bool bln){} 你能把这个方法传递给SomeDelegate的构造器做参数,因为它们有相似的特征。 SomeDelegate sd = new SomeDelegate(SomeFunction); 现在,sd引用了SomeFunction,换句话说,SomeFunction被注册到了sd。如果你调用sd,那么SomeFunction也将被调用。紧记已注册方法的含义。后面,我们将引用它。 sd("somestring", true);

    既然你已经知道怎么使用委托,下面让我们来理解事件……

    理解事件 一个按钮是一个类,当你点击它的时候,click事件被触发。

    一个计时器是一个类,每毫秒触发一个tick事件。 想要知道发生什么了?让我们通过一个例子去学习

    这是一个假定:我们有一个类Counter。这个类有一个CountTo(int counTo,int reachableNum)方法,从0到countTo计数,并且只要计数到reachableNum这个数时会触发一个NumberReached事件。

    我们的类有一个事件:NumberReached。事件是委托的变量。我的意思是,如果你声明一个事件,同时也声明某一类型的委托,并且要把event关键字放在声明前面,看起来应该是这样的: public event NumberReachedEventHandler NumberReached;

    在上面的声明中,NumberReachedEventHandler只是一个委托。也许它更应该叫NumberReachedDelegate,但是注意到微软并没叫MouseDelegate或PaintDelegate,而是叫MouseEventHandler或PaintEventHandler。把它命名为NumberReachedEventHandler而不是NumberReachedDelegate,这只是一个惯例,并且我们经常看到的EVentHander 是微软预定义的一个委托。

    你了解了我们声明事件前,需要先定义相应的委托(事件处理者)。

    它看起来可能是这样的:

    public delegate void NumberReachedEventHandler(object sender, NumberReachedEventArgs e);

    如你所见,我们委托的名字是:NumberReachedEventHandler,它的特征是返回一个void值,两个参数类型分别为object和NumberReachedEventArgs。

    如果你在某处实例化这个委托时,你所传递的方法必须和它具有一样的特征。

    在你的代码中,你使用过MouseEventArgs或PaintEventArgs去确定鼠标的位置,它在向哪移动,或某个物体的图形属性触发Paint事件么?实际上,在从EventArgs类继承的类中我们提供给使用者我们的数据。

    例如,在我们的例子中,我们提供那个可达的数。下面是这个类的声明:

    public class NumberReachedEventArgs : EventArgs

    {

    private int _reached;

    public NumberReachedEventArgs(int num) { this._reached = num; }

    public int ReachedNumber { get { return _reached; }}

    }

    如果不需要提供给使用者任何信息,我们可以直接使用EventArgs类。

    现在,所有的事都已经准备好了,下面让我们来看一下Counter类的内部实现:

    namespace Events

    {

    public delegate void NumberReachedEventHandler(object sender, NumberReachedEventArgs e);

    ///

    /// Summary description for Counter. ///

    public class Counter

    {

    public event NumberReachedEventHandler NumberReached;

    public Counter()

    { // // TODO: Add constructor logic here // }

    public void CountTo(int countTo, int reachableNum)

    {

    if(countTo < reachableNum)

    throw new ArgumentException( "reachableNum should be less than countTo");

    for(int ctr=0;ctr<=countTo;ctr++)

    { if(ctr == reachableNum)

    { NumberReachedEventArgs e = new NumberReachedEventArgs( reachableNum);

    OnNumberReached(e); return;//don't count any more } } }

    protected virtual void OnNumberReached(NumberReachedEventArgs e)

    { if(NumberReached != null)

    { NumberReached(this, e);//Raise the event }

    } }

    在上面的代码中,如果到达预期的数时就触发一个事件。在这里我们需要考虑很多事情:

    1、触发一个事件是通过调用我们的事件(NumberReachedEventHandler的一个实例)完成的。 NumberReached(this, e); 这样,所有已注册的方法都将被调用。

    2、我们给已注册的方法数据通过以下代码: NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum);

    3、一个问题:我们为什么通过OnNumberReached(NumberReachedEventArgs e)方法间接的调用NumberReached(this,e)事件?

    为什么我们不用下面的代码:

    if(ctr == reachableNum)

    { NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum); //OnNumberReached(e);

    if(NumberReached != null)

    { NumberReached(this, e);//Raise the event } return;//don't count any more }

    好问题!如果你想知道为何间接调用,请看OnNumberReached方法的特征:

    protected virtual void OnNumberReached(NumberReachedEventArgs e)

    你看到了,它是保护方法,意味着从这个类继承的类(子类)中它是可以调用的。 同时它也是虚方法,意味着子类可以改写它的实现。

    那是非常有用的。想象一下你正在设计一个从Counter类继承的类,通过改写OnNumberReached方法,可以在事件触发之前方便的增加一些附加的工作。例如: protected override void OnNumberReached(NumberReachedEventArgs e) { //Do additional work base.OnNumberReached(e); }

    注意如果你不调用base.OnNumberReached(e),那么事件永远也不会触发。当你继承了一些类时,你可能想要去除一些事件,这时这样也许就有用了。有趣的窍门,哈?

    一个真实的例子,当你创建一个新的ASP.NET 应用程序时,你去看看后台产生的代码,你会发现你的页面继承自System.Web.UI.Page类,而且有一个叫OnInit的保护虚方法。其中在里面有一个InitializeComponent()方法被调用用来做一些附加的工作,然后再调用基类的OnInit(e)方法:

    #region Web Form Designer generated code

    protected override void OnInit(EventArgs e)

    { //CODEGEN: This call is required by the ASP.NET Web Form Designer. InitializeComponent(); base.OnInit(e); }

    ///

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    ///

    private void InitializeComponent()

    { this.Load += new System.EventHandler(this.Page_Load); }

    #endregion

    4、注意NumberReachedEventHandler委托,它是定义在Counter类外,Events命名空间内的,对所有类都可见。

    好了,是时候实践一下我们的Counter类了。 在我们的应用程序中,我们有两个文本框:txtCountTo和txtReachable 这里是btnRun按钮点击事件的事件处理代码:

    private void cmdRun_Click(object sender, System.EventArgs e)

    { if(txtCountTo.Text == "" || txtReachable.Text=="") return;

    oCounter = new Counter();

    oCounter.NumberReached += new NumberReachedEventHandler( oCounter_NumberReached); oCounter.CountTo(Convert.ToInt32(txtCountTo.Text), Convert.ToInt32(txtReachable.Text));

    }

    private void oCounter_NumberReached(object sender, NumberReachedEventArgs e)

    { MessageBox.Show("Reached: " + e.ReachedNumber.ToString()); }

    这里是初始化某个事件的事件委托的语法: oCounter.NumberReached += new NumberReachedEventHandler( oCounter_NumberReached);

    现在你应该了解到我们正在做什么。我们初始化了NunberReachedEvnetHandler委托(也可以对其他对象)。

    注意我上面提及的oCounter_NumberReached方法签名的相似性。

    现在来看看我们用=代替+=的情形。 委托是特殊的对象,因为它可以保持多个对象的引用(这里是多个方法)。

    例如,如果你有另一个方法叫oCounter_NumberReached2,而且签名和oCounter_NumberReached一样,那么两个方法都可以象下面那样引用: oCounter.NumberReached += new NumberReachedEventHandler( oCounter_NumberReached);

    oCounter.NumberReached += new NumberReachedEventHandler( oCounter_NumberReached2);

    现在,当事件被触发,一个接一个的方法将被调用。 如果在你的代码某处,你不想在NumberReached事件触发时调用oCounter_NumberReached2,

    你可以这样做: oCounter.NumberReached -= new NumberReachedEventHandler( oCounter_NumberReached2);

    event关键字 许多人也许会问:如果我们不用event关键字会怎么样? 使用evnet关键字可以阻止任何一个委托的使用者把它设为null。

    为什么这是重要的?想象一下,一个客户把我类中的其中一个方法注册到委托调用链表,其他客户也这样做,这不会出错。

    现在,如果有另一客户用=代替+=给委托新注册一个方法。这将会把原来的委托调用链表清空,并且创建一个全新的单一的委托在委托调用链表中。

    这时其他客户将无法接收回复信息。

    关键字event正是针对这一问题提出的,如果我在Counter类中加上event关键字,并试着编译下面的代码,将产生一个编译器错误信息:

    总之,event关键字在委托实例上加了一层保护,保护客户的委托以免被重新设置及委托调用链被清空,这样就只允许对委托调用链进行添加或移除操作。

    结尾 别忘了在你应用程序的构造函数中声明以下内容,而不是在cmdRun_Click事件处理代码中。我那样做仅仅只为了简单。;-)

    public Form1() {

    // // Required for Windows Form Designer support

    // InitializeComponent();

    // // TODO: Add any constructor code after InitializeComponent call

    //

    oCounter = new Counter();

    oCounter.NumberReached += new NumberReachedEventHandler( oCounter_NumberReached);

    oCounter.NumberReached += new NumberReachedEventHandler( oCounter_NumberReached2);

    提供的源代码就是这样子的

  • 相关阅读:
    HUAWEI防火墙双出口据链路带宽负载分担
    HUAWEI防火墙双出口根据链路优先级主备备份
    HUAWEI防火墙双出口环境下私网用户通过NAPT访问Internet
    如何实现IP话机接入交换机?
    WLAN-AC+AP,动态负载均衡用户量,避免某一个AP负载过重
    WLAN-AC+AP射频一劳永逸的调优方式
    中大型企业有线无线用户统一接入(实施笔记)
    js获取当前时间,返回日期yyyy-MM-dd
    cookie和token都存在在请求头header中,有什么区别,为什么建议使用token?
    vue中cookie的使用——将cookie放在请求头header中
  • 原文地址:https://www.cnblogs.com/hanmos/p/1947287.html
Copyright © 2011-2022 走看看