当前位置: 首页 > news >正文

[翻译]简单谈谈事件与委托

原文地址:http://www.codeproject.com/csharp/events.asp
源代码下载:/Files/edgar-sun/events_src.zip
演示文件下载:/Files/edgar-sun/events_demo.zip
作者:Maysam Mahfouzi  
原文发布日期:2003/8/16
原文更新日期:2005/5/14

内容
介绍
什么是委托?
理解事件
event关键字
结尾

介绍
   当我设法学习事件与委托时,我阅读了很多文章去完全地理解它们并使用它们,现在我想把我学到的展现在这里,其中有很多知识你也需要学习。
什么是委托?
   委托和事件是紧紧联系在一起的。委托是函数(方法)指针,更确切地说,委托保持方法的引用。
   委托是一个类。当你创建它的实例的时候,你传递将被委托调用的方法名(做为委托构造器的参数)。
   每个委托都有一个特征。例如:

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,这只是一个惯例,了解?很好!
   你了解了我们声明事件前,需要先定义相应的委托(事件处理者)。它看起来可能是这样的:

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>
    
///  Summary description for Counter.
    
///   </summary>
     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);
}
///   <summary>
///  Required method for Designer support - do not modify
///  the contents of this method with the code editor.
///   </summary>
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);

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

转载于:https://www.cnblogs.com/nicholasun/archive/2007/12/30/1021155.html

相关文章:

  • WCF学习(一)
  • 新年第一天开通我的博客
  • 使用fail2ban保护系统一例(ssh)
  • javascript 中的xml dom
  • .NET中统一的存储过程调用方法(收藏)
  • Sun全球媒体高峰论坛开幕 CEO称重回快速增长轨道
  • 电脑总是丢失文件
  • ATL7.0 中 已经没有了CComModule
  • C++编程规范
  • RedHat ip配不上
  • Silverlight2.0 将直接支持中文
  • Win32汇编学习笔记(二)
  • 重新回归
  • 企业信息化:Web2.0带来的启示
  • 综合布线图书大全
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【node学习】协程
  • Apache Pulsar 2.1 重磅发布
  • happypack两次报错的问题
  • in typeof instanceof ===这些运算符有什么作用
  • js写一个简单的选项卡
  • npx命令介绍
  • Python3爬取英雄联盟英雄皮肤大图
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • spring-boot List转Page
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 力扣(LeetCode)965
  • 聊聊flink的BlobWriter
  • 前端知识点整理(待续)
  • 小李飞刀:SQL题目刷起来!
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • 再谈express与koa的对比
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • 组复制官方翻译九、Group Replication Technical Details
  • ​水经微图Web1.5.0版即将上线
  • !!java web学习笔记(一到五)
  • "无招胜有招"nbsp;史上最全的互…
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • (3)nginx 配置(nginx.conf)
  • (31)对象的克隆
  • (C语言)逆序输出字符串
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (八十八)VFL语言初步 - 实现布局
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (分布式缓存)Redis持久化
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • 、写入Shellcode到注册表上线
  • .net的socket示例
  • .pop ----remove 删除
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题
  • @在php中起什么作用?