您的位置:首页 > 其它

.Net事件与委托

2011-09-20 11:36 260 查看
原文地址: http://space.itpub.net/14325734/viewspace-450242

【简介】

在前述的博文中我们总是提到事件和委托,而Windows编程决离不开"事件",现在当使用.Net编程时"委托"又是必不可少的。在这里我们对事件和委托做一个总结,向您呈现到底什么是.Net事件和委托、两者的关系以及如何熟练使用它们。

【什么是委托 delegate】

.Net下事件和委托总是一个整体,必须一起来阐述。在前面博文中我们已经或多或少的介绍过,其实委托就是一个函数指针,一个委托型变量就是在维护一个函数列表,保存函数的地址或者称为引用。

delegate 实际是一个类,当创建一个委托时,需要向delegate的构造函数传递一个函数名。

任何委托都必须有特定的签名,例如定义一个委托类型SomeDelegate:

delegate int SomeDelegate(string s, bool, b);

当我们说委托、函数或者方法具有什么样的签名时,就是指这个委托、函数或者方法需要传递什么类型的参数以及返回值的类型等等。当初始化一个委托时你必须传递一个实际的函数。这时,一个非常重要的事情需要注意:只有与委托具备完全一致的签名的函数才能进行传递,例如名为SomeFunction的函数:

private int SomeFunction(string str, bool bln){...}

这个函数是可以传递给前述SomeDelegate委托的,因为他们具备相同的签名!

SomeDelegate sd = new SomeDelegate(SomeFunction);

好,现在名为sd的SomeDelegate类型的变量已经指向SomeFunction,或者说SomeFunction已经注册到sd!当调用sd时,SomeFunction将会被调用。注意,为什么说SomeFunction注册到sd呢?稍后,我们将涉及到这个问题。

sd("somestring", true);

【理解事件 event】

# Button是一个类,当你单击它的时候,click事件被激活。

# Timer也是一个类,每次毫秒逝去都会激活tick事件。

......

让我们通过一个经典的计数示例程序来理解这其中的奥秘。我们有个Counter类,这个类有有个方法名为CountTo:

public void CountTo(int countTo, int reachableNum)

它从0开始计数到自定义整数变量countTo,当到达reachableNum时将触发名为NumberReached的事件。

事件其实就是委托类型的变量,也就是说如果想声明一个事件的话,你必须先声明一个委托类型的变量,然后将event关键字放在声明体的前部,例如:

public event NumberReachedEventHandler NumberReached;

在上面的声明中NumberReachedEventHandler就是一个委托。既然是个委托也许我们应该写为NumberReachedDelegate,但是我们注意到微软并不以MouseDelegate或者PaintDelegate等格式来命名事件,而是以MouseEventHandler和PaintEventHandler的形式来命名,所以我们也按照惯例来把NumberReachedDelegate命名为NumberReachedEventHandler。

所以,当我们声明一个事件时必须提前定义好一个委托,例如:

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

NumberReachedEventHandler具有object和NumberReachedEventArgs类型的传递参数以及void返回值的签名,当实例化这个委托时,传递的函数签名必须与该委托的签名保持一致。

现在来看一下NumberReachedEventArgs,设想当你想确定鼠标移动到的位置以及想存取Paint事件的Graphics属性时都会分别用到MouseEventArgs和PaintEventArgs,实际上这两个类都是继承自EventArgs类,来向用户传递事件发生时的相关数据。在我们的示例中,我们会传递那个到达的数即reachableNum:

public class NumberReachedEventArgs : EventArgs

{

private int _reached;

public NumberReachedEventArgs(int num)

{

this._reached = num;

}

public int ReachedNumber

{

get

{

return _reached;

}

}

}

如果在事件发生时没必要传递数据的话,我们可以直接使用EventArgs,而不必再自定义。

好,现在来看看我们的Counter类:

namespace WindowsFormsApplication1

{

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

public class Counter

{

public event NumberReachedEventHandler NumberReached;

public Counter()

{

}

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;

}

}

}

protected virtual void OnNumberReached(NumberReachedEventArgs e)

{

if (NumberReached != null)

{

NumberReached(this, e);

}

}

}

}

在上述的代码中,当到达指定的数值时事件就会被触发。这里有许多事情需要考虑:

# 通过调用NumberReached(即NumberReachedEventHandler委托类型的实例)来触发事件;

NumberReached(this, e);

这时所有已经注册的函数都将被调用执行。

# 我们定义事件中需要传递的数据:

NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum);

# 也许你会问:为什么我们不直接调用NumberReached(this, e),而是间接的调用OnNumberReached(NumberReachedEventArgs 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)

# 这个方法是protected的,那么在所有的继承类或者说子类中都是可以访问它的

# 这个方法是虚拟的,也就是说它可以在任何继承类或者说子类中被重载

这样做是非常有用处的!假如你又设计了一个继承自Counter的类,通过重载OnNumberReached方法,你可以在事件被激活前做一些附加的操作,例如:

protected override void OnNumberReached(NumberReachedEventArgs e)

{

//做一些额外的工作

base.OnNumberReached(e);

}

# 注意,如果没有调用基类的base.OnNumberReached(e),那么事件将永远不会在这个继承类中被触发!这样做的好处就是你可以在继承类中屏蔽不需要的事件!!

# 注意,NumberReachedEventHandler委托定义在我们的Counter类之外,但却是在同一命名空间下,所以这个委托对该命名空间下的所有类都是可见的。

好了,现在该是使用我们创建的Counter类的时候了。在示例程序中,我们有两个名为txtCountTo和txtReachable的textbox控件,界面如下:





下面是btnRun的单击事件处理:

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

{

if (txtCountTo.Text == "" || txtReachable.Text=="")

return;

Counter = 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("事件1已到达: " + e.ReachedNumber.ToString());

}

初始化一个事件处理的语法如下:

oCounter.NumberReached += new NumberReachedEventHandler(

oCounter_NumberReached);

现在,你该明白是怎么回事了吧?没错,我们刚刚实例化了NumberReachedEventHandler类型的实例。请注意oCounter_NumberReached方法的签名和我们前述的委托签名非常相似。而且再请注意,我们使用了+=来代替=!这就是为什么委托是一种特殊的类,并且可以维护超过一个对象的引用,在本文中委托维护一个函数调用列表。例如,如果你还有另外的方法名为oCounter_NumberReached2,并且与oCounter_NumberReached具有同样的函数签名,那么可以通过这种写法将所有方法都注册到这个委托上来:

oCounter.NumberReached += new NumberReachedEventHandler(

oCounter_NumberReached);

oCounter.NumberReached += new NumberReachedEventHandler(

oCounter_NumberReached2);

当激活这个事件后,所有已注册的方法都将依次被执行!如果不打算在事件发生时还执行oCounter_NumberReached2的话,你可以这样取消这个方法:

oCounter.NumberReached -= new NumberReachedEventHandler(

oCounter_NumberReached2);

【event 关键字】

有很多人都会问:如果我在委托前不使用event关键将会发生什么呢?

本质上来讲,声明event可以阻止委托被赋值为null!这真的很重要吗?当然!设想,如果一个委托前面没有注明event,向委托注册一个函数时肯定是需要用到"+=",但是如果写成"="的话就会对委托重新赋值,这时委托已经被一个全新的实例所取代,并且丢弃了已经注册到委托的所有函数,所有需要执行的函数或方法都无法被执行。致命的是,系统也不会因此报告任何编译错误,甚至是运行时错误!面对这种情形,我们就需要event关键字来进行解决。当我们把

public event NumberReachedEventHandler NumberReached 前的event关键字去除,我们可以这样写:

oCounter.NumberReached = new NumberReachedEventHandler(oCounter_NumberReached);

这样写编译器决不会报错,但是却不是我们想要的!如果保留event关键字,上述赋值语句在编译时就会报错!提示必须使用+=或者-+,例如:





总之,event关键字是委托实例的一个保护层,它可以阻止对委托进行清空操作,而只是允许向委托调用列表添加和移除对象。

本文示例程序在Windows xp sp3 + .Net 2.0 + vs2008 sp1下编译调试通过。

【下载本文附带源码】
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: