现在的位置: 首页 > 综合 > 正文

NET基本探究系列——委托

2012年06月05日 ⁄ 综合 ⁄ 共 4265字 ⁄ 字号 评论关闭

l       认识委托

       如果说“类函数”是减轻程序员编写的代码量,根据不同参数输入、经过统一过程计算的“公式化”模式,那么“委托”远远不仅如此——它不仅具备“类函数”的特征,同时还允许程序设计者在同参、同返回值的情况下“动态”改变函数体内容,并且可以实现“跨类调用”,所以“委托”无愧于是真正的“动态函数体”。这个特性在微软的Framework框架下有着广泛的应用(诸如“事件驱动机制”、Lambda运算式、表达式树、Linq等)。今天我们将初步了解委托的语法定义,向大家介绍“纯委托”、“匿名委托”、“多路委托”,以及利用委托设计“观察者模式”的简化应用。

 

l       纯委托

C#中,委托的定义必须是这样子的:

 

[修饰符] delegate [返回类型] 委托名(参数列表……;

 

一个简单的纯委托可以这样挂接和使用:

委托名 委托实例=new 委托名(被委托函数),注意“挂接”时不能有参数列表!

 

委托实例([参数列表];

 

下面给出一个具体的例子:

 

【例1】设计一个控制台应用程序,当输入端输入1,执行两个数的加法;输入2的时候执行两个数的减法(不考虑异常和其它输入等情况)。

【分析】本题的计算过程是受用户控制的(即函数过程体是动态可以变化的),但是输入、输出参数类型不发生变化。因此可以使用委托,进行动态方法调用:

 

Code

 

     显然,上面程序根据用户输入的数值“动态”调用不同的过程、却具有相同函数参数列表,以及返回值的“类函数”,实现了“动态改变函数体”的目的。

       或许有些读者要发问了——难道我就不能直接调用两个函数嘛?干嘛那么复杂?——是的,这道题是为了初步解释纯委托的简单应用,打比方而已。在真正的开发中,我们往往要在不改变原有的代码情况下,动态结合我们自身增加的过程下面就来看一个稍微复杂点的示例:

 

       【例2】假设有一个List<int>数组(有110个数,这儿是泛型的用法,您只要先知道在这里的List中只能存放int以及可以转换成该类型的数据类型)。我们要设法求出偶数的数字,并输出打印。

       【分析】这个问题似乎也可以使用for循环来解决,不过微软为List提供了这样一个方法:

FindAllpublic delegate bool Predicate<T>(T obj)

       如果您不了解泛型,那么您就可以把这个函数近似理解成如下对于int的委托参数:

FindAllpublic delegate bool Predicate (int obj)

 

       似乎微软已经为你完成了FindAll的函数部分过程(返回值是List<int>),但是判定过程还是你来写。显然,如果你要使用这个方法,你就不能简单地调用自定义的函数进行直接寻找输出了。而是必须按照它的规定来——首先看,这个委托要求输入参数是一个int,输出是一个bool,结合题目要求(求偶数),您应该会直观想到偶数的判定式“num%2==0所以你可以写一个函数如下:

 

public bool IsEvenNum(int num)
{
    
return num%2==0;
}
    然后开始调用,传入该函数(如下):
foreach(int i in XXXXList.FindAll(new XXXXClass().IsEvenNum))
{
    Console.WriteLine(i);
}

 

注意:“XXXXClass”是这些方法所在的类名,“XXXXList”是您定义的某个存放int的泛型List

       通过这个例子,您应该明白了解了“动态函数体”(委托)的厉害之处——它可以在不改变原来代码的基础上,对于同参同返回值的过程实现自定义,而且可以和微软内部的FindAll方法进行无缝对接(纯粹返回一个bool不足以实现偶数集合输出,微软在FindAll内部只是简单地调用了该委托,利用外部方法实现“自定义”的函数体功能,代码参考如下):

 

Code

 

l       匿名委托

       事情总是呈现多面性的——函数除了可以被同参、同返回值的委托“挂接”外自然可以根据其特有的访问修饰符被其它类、函数调用,我们自然会认为——如果一个函数多次、频繁被外部类(或内部)调用,则这个函数的确有其存在的意义(因为减缓了重复、机械式编码疲劳)。现在的问题在于,如果一个函数只是为了“委托”的需要而刻意编写(比如仅仅为了像上面的例子一样求偶数),是不是有点不值得?不情愿?

       但是“函数”又不得不写——怎么办?“留之无用,弃之可惜”这种“鸡肋”事件我们希望只是发生在曹操身上,悲剧不希望在我们这里重演。所以微软对于“只被特定委托调用”的函数设计一种特有的定义方式——匿名委托(匿名委托函数)

 

       匿名委托的定义:

       委托名  委托示例=new 委托名

(

       delegate (参数列表)

       {

              过程……

              [返回值]

       }

 

)

 

       上面的纯委托就可以做如下改变:

 

XXXXList.FindAll(delegate (int num){return num%2==0;});

 

l       多路委托

       除了“纯委托”、“匿名委托”之外,我们还可以将多个已经挂接到不同类的函数(必须都复合委托的同参列表同返回值),然后统一用一个委托逐一遍历调用,这种方式被成为多路委托(或者多播委托。“多路委托”的语法定义如下:

 

委托名 委托实例=new 委托名(被委托函数1

委托实例+= new 委托名(被委托函数2

委托实例+= new 委托名(被委托函数3

………………

要移除某个委托,只需使用-=替换+=即可。

 

       【例】使用“多路委托”实现计算输出数字30SinCosTan结果

       【分析】SinCosTan都具备输入参数“double”,输出参数“double”共同特性,因此可以使用一个统一委托进行挂接。代码如下:

 

Code

 

实际上,C#delegate是一个抽象类,其中包含了存放具备相同参数列表和返回值的委托数组,并且重写了+=和-=方法,从而使得您可以使用这些符号动态方便地添加、移除这些函数。要获取多个委托的函数进行遍历,您可以使用GetInvocationList()方法。

 

l       借助“委托”设计观察者模式——模拟Windows的音量调节器

       “观察者模式”是经典设计模式中的一种,也是应用比较广泛的一种优秀思想。其主要实现“事件驱动”机制,同时实现不同类(对象)之间的同步问题。这儿我们以微软的“音量调节器”举一个例子——在您进入Windows桌面之后是不是注意到右下角有一个小喇叭?双击该喇叭若干次,会产生相同的窗口,不过无论你改变哪个窗口的滑动条,其余的一定随之发生变化——这就是“联动效应”,观察者模式的典型运用。

       不过要实现这个功能不是很简单的,因为最麻烦的问题在于初始化了若干窗口,这些窗口的进度条如何做到完全同步(每个窗体的实例是独有的,不是共享)?

       唯一的解决方案就是另外设计一个类,专门用于监听是否有窗体产生(有的话加入其监听队列),并且具备一个同步方法(将所有的成员进行同步处理)。

       现在我们假设用VS设计器生成MusicForm,有一个Button,点击将生成与自己一模一样的窗体,有一个拉杆,拖拉将使得其它窗体和自身的拉杆值保持同步。要实现这样一个功能,其结构可以使用UML示例,大致图如下:

  

   

     具体NotifyListener类代码如下:

 

Code

抱歉!评论已关闭.