这篇文章来源于CodeProject日报推荐,是2013年6月c#技术的最佳文章(原文地址 http://www.codeproject.com/Articles/602210/The-Power-of-Delegates-in-Csharp )。文章内容就是介绍了委托及其简单应用,难度不大,现在拿过来与大家分享。不过由于第一次翻译文章,加之本人水平不高,很多地方不能做到翻译上常说的“信达雅”,如有谬误,还请批评指正。
大家好,这回我想介绍一个c#的特性,一个我们大多数人(或者说菜鸟程序员)并没有完全利用的特性。在c#中,存在着一些我们没有听过或者从未想象的特性。由于对c#能量的无知,本来使用一行简单有效的代码可以实现的需求,我们却使用100行代码来达到目标。所以,在这篇文章中,我来介绍一下delegate,这个c#中这个充满能量的特性。
如果有人问你什么是委托,你可以简单的告诉他:一个委托是一个函数指针。或者我们可以说:一个委托是保持对某个函数引用的一种类型或者一个对象。
为了使一个委托指向某个函数我们必须满足下列条件:这个函数的声明必须和指向它的委托的声明一致。
下面让我们看看一个委托如何指向函数。可能你和我一样已经厌倦了所有示例和文章中黑屏的控制台程序,所以在这个示例中,我将要创建一个ASP.NET程序来展示委托的应用。
下面这个示例程序将完成计算圆面积的功能。
看上去不错吧?
完成界面后,我们写一个计算面积的函数。
上一幅图中,你能看到这个计算圆面积的方法。可能你会想,这里边有什么新内涵?是的,没有什么新东西:)。如果运行这个程序,我们就能根据输入的半径得到相应的面积。
定义并且使用一个委托总共分三步:
1 定义
2 初始化
3 调用
上图中,你可以看到我创建里一个名为DelCalcArea的委托,然后在Calculate按钮的单击事件中,初始化了DelCalcArea这个委托的一个对象并且将CalculateArea 方法赋值给该对象。我希望到现在思路还不乱。
为了计算圆面积,我们并不直接在按钮事件里调用CalculateArea 方法,而是调用附属了该方法的委托实例。可以看到在截图中,我使用了委托实例的Invoke方法,如果你看Invoke方法的智能提示,你将得知它接受一个int型变量并且返回值为double类型,这与CalculateArea 一致。委托实例的调用就像其他正常的方法调用一样,给想要显示结果的标签赋值就可以了。
现在为止还是比较简单吧?唯一需要注意的地方是,我们使用委托实例的时候不是直接(向方法一样)调用,而是使用Invoke方法。
如果你阅读过有关委托的文章,你可能听说过多播委托。什么是多播委托?一言以蔽之,“在一行代码里调用多个方法”或者“多播委托就是一串委托的列表”。
我们怎样才能在一行代码中调用多个方法呢?我们需要把有同样函数声明的多个方法赋值给同一个委托。举个例子,我要实现这样一个需求:我需要在一行代码中执行两个语句,其中一个语句计算圆的面积,另一个计算正方形的面积,最后使用同一个输入参数传入两个方法。所以我们的代码看起来是这样:
从以上代码能够看出,委托的声明和两个方法的声明都是匹配的,另外符号+=用来把方法和委托实例联系起来。下面让我们看一下输出:
是的,它(按照需求)运行成功了:)。就像我们使用+=来向委托实例添加函数一样,我们同样可以使用-=来去除添加的函数。
借用同一个例子,来看看我们如何从委托实例中去除已经添加的方法。
接上一个例子来看,我向委托实例添加了两个方法,现在我将从委托实例delCalc 中去除CalculateSquareArea 方法,所以代码会看起来像这样:
看到我怎样移除方法了吗?所以现在我们的期望输出将是什么样的?方法CalculateSquareArea 不再执行并且结果也不会再显示在输出中,对吧?
像预期的一样...
在之前的小节中我试着给大家了一个对委托的整体印象,在这个小节中,我又讲讲委托与事件的联系。
如果存在一个事件,那么必将同时存在处理这个事件的事件处理程序。当我们使用用户控件把事件参数推入父页面时,通常需要使用委托处理。
我们来构建一个使用场景:我想要开发一个圆面积计算程序,并且我决定把其作为用户控件。这个计算程序包括一个用于输入圆半径的输入框和一个用于确定计算的按钮。父页面上将使用一个标签来显示结果。也就是说,用户控件本身并不提供显示功能。所以问题就是,我们能在父页面中得到控件的运算结果吗?
答案当时是可以。我们在事件和委托的帮助下就能完成它。
首先,建立一个asp.net项目,我项目的命名是CsharpFeatures。在这个项目里我添加了一个名为ChildUserControll的用户控件。
接着,我们来设计用户控件的UI,它看起来将像这样:
让我们来探索内部代码是如何完成需求的。为了让计算事件发生,我们需要注册一个事件,对吧?我们可以把计算的代码添加到Calculate按钮单击的事件中。接下来,试想一下,我们可以在父页面获得用户控件ChildUserControll的按钮单击事件吗?答案是否定的。我们该怎样做?我们需要实现相应的事件,事件处理程序以及事件的参数,接下来我们来一起看看以上如何实现,并且委托在其中是如何发挥作用的。
就像我之前提到的,我创建了一个用户控件。现在,在用户控件中,我要添加一个事件:
上面的截图中我在Page_Load方法之上,添加了一个名为MyEvent的事件。这个事件将允许使用了该用户控件的页面公开访问。你注意到了MyEvent的类型了吗?它的类型是MyEventHandler。你觉得这是一个系统预定义的类型吗?不,实际上MyEventHandler是一个委托类型。我们做的就是将MyEvent事件的类型定义为MyEventHandler。所以现在我们就能得知MyEvent事件输入参数和返回值,实际上就和MyEventHandler的定义一致。下面我们看看MyEventHandler的定义:
看上面的委托类型定义,是不是觉得和你熟悉的什么东西很相似?对了,看下面的Page_Load方法声明,也许就是你的答案。不同的一点是Page_Load方法中的参数是EventArgs ,而我们委托中的是MyEventArgs。EventArgs 是.net类库中定义好的,而MyEventArgs 是我自己定义的。也许你现在会有疑问,MyEventArgs中有什么,而且什么样的类型可以作为MyEvent事件的参数呢?
我们的MyEventArgs可以包含某函数功能执行之后的结果数据。
所以在我们的程序里就是圆的面积。
接下来,我将展示MyEventArgs 的类。这个类继承自System.EventArgs ,还包含了一个属性Area。
为了便于理解总体逻辑,以上我对事件的解释实际上是与实现过程相反的。正确创建事件的步骤是首先创建MyEventArgs ,接着定义MyEventHandler,最后才是定义MyEvent 。希望大家理解这个顺序。到现在为止,我们只是创建的基础的代码,没有进行任何实际的业务工作。但是没有基础结构,我们也无法实现业务需求。
回顾下我们之前的目标,我们要通过MyEventArgs的帮助来从用户控件中提取计算结果。首先我们要做的是,设置MyEventArgs的Area 属性。
可以看到,代码中是我如何赋值的。接着要做的是,怎么才能把MyEventArgs的结果公开出来,一种方法就是通过事件,调用事件时将我们刚才创建的MyEventArgs实例作为参数传入
这样我们就实现了在运算完成后调用事件。截图中我启用了调试器来跟踪传入结果,这里你能清楚的看到objEvArgs的传入结果。看起来没什么错误。
接着,我们转到控件的调用页面,接着来实现标签显示的效果。我们要在Default.aspx拖入我们的用户控件,像下图
然后我们在Default页面中,加入一些必要的代码。
可以看到,我们定义的事件已经能够显示在只能提示中,那就让我们添加事件处理程序到这个页面中吧!
小技巧,在将代码写到如图一样的时候,点击键盘的TAB可以直接生成相应的事件处理程序代码。
在创建的事件处理程序中,向当前页用于显示结果的标签赋值。这就已经结束了,就是这么简单。
让我们运行下,看看结果。
哇,让人惊叹的奇迹。一个委托创造的奇迹。
现在我在深入一点,介绍下我们如何对程序进行合理的松耦合扩展。
你是否注意到在我的解决方案管理器中有一个叫做Calculator的项目。接下来,我就要使用这个项目来开发一个能对两个整数进行加,减,乘运算的计算器应用,Calculator项目将作为计算层,用于处理这个应用中的计算逻辑部分。
我们来看看计算层里有哪些内容。我先在这个项目里添加了一个MyCalculator类,用作创建计算逻辑。那么,什么将在这个计算器中用来解耦。这里涉及到大量的技巧,我们先把他放一边,来考虑我们如何建立一个具有表示层和业务层的两层程序。
我们为调用计算层的客户端开发三个方法来分别处理不同的操作,这里会用到条件语句。如果我们想要程序里添加更多的功能,就要在计算层里写一个新方法(比如除法),并且为了适配计算层的修改,还要在相应的调用处进行修改。
UI层和计算层(业务层)紧密的连在一起。另外,因为使用了公共方法,所有计算层的方法或操作都暴露给了调用端。我们怎样才能避免这种情况?对于这个场景,我们可以使用几种方法来处理。在本篇中,我们着重介绍如何用委托来处理。
我们回到例子中,先来看一下UI。
两个textbox用于输出数字型的值,一个下拉列表来展示操作符号。我们在回到,计算层,看看我进行了哪些工作。和之前一样,我们要借助委托来实现实际计算方法的隐藏。
就如你所见,我声明了一个名为DelegateMyCalculator 的委托,它接受两个整形的参数,并返回一个整形的数据。另外,你还能看见这个类中定义了三个私有方法。接着,我们来把委托和方法联系起来。这里有个技巧,像之前一样(事件处理函数),我们不直接将方法赋值给委托,而是创建一个接受字符型参数,并且返回值为委托类型的方法。
上图就是技巧的实现过程。先创建了一个Calculate方法,并且在其中定义了一个DelegateMyCalculator 类型的局部变量,并赋值为空。你能清楚的看到,这个委托实例只根据输入的参数进行了一次赋值。让我们转到UI层,看看调用是如何进行的。
现在你可能会有疑问,我们如何调用委托方法?如何在UI层向委托方法提供输入值?目前的Calculate 不是只能接受一个字符型参数吗?让我们看看UI层的实现。
你可以在智能提示里看到唯一一个方法Calculate ,其他计算层的计算方法都不再公开。我们的一个目的达到了。
然后,我把下拉列表中被选中的值,传递给了Calculate 方法。
最后使用委托实例的Invoke方法,将两个整形参数传入,并把返回值赋值给界面上的显示控件。是不是很简洁?我们运行下看看结果。
这个小程序工作很正常。我们现在可以说,我们的程序是一个松耦合的程序,因为如果我们再添加更多的功能,我们就直接在计算层添加一个方法,而表示层只需要在下拉列表直接添加类型就可以了。按照这个思路,你在开发多层应用程序时,如果需要扩展功能,只需要在必要的位置开展工作,而不需要在与它相关的层次上进行过多的额外工作。
可能是由于第一次完整的翻译一篇文章,中间过程感到颇为曲折,不是文章中的技术点难,而是觉得作者有点“话唠”。几乎每幅图片上下紧挨图片的那句话表达的意义都相同,而且大量的自问自答。可能是作者比较注重与读者的互动吧。开始我还能做到每句话都紧跟作者意思,后来发现这样太浪费时间了,啰嗦的语句完全影响的简练中文的美感:-D,所以决定,以后的翻译不再逐句翻译,把传递作者原意作为标准。
最后,还是希望这篇文章能帮助到需要的朋友吧。
下次将带来, c#中的协变 Covariance in C#
http://www.codeproject.com/Tips/631360/Covariance-in-Csharp
will_power