委托是一种可以把引用存储为函数的类型委托的声明非常类似于函数但是不带函数体且要用delegate关键字委托的声明指定了一个返回类型和一个参数列表在定义了委托之后就可以定义该委托类型的变量接着把这个变量初始化为与委托有相同返回类型和参数列表的函数引用之后就可以运用委托变量调用这个函数就像该变量是一个函数一样
有了引用函数的变量之后还可以执行不能用其他方式完成的操作
将方法作为方法的参数 我们先不管这个标题如何的绕口也不管委托究竟是个什么东西来看下面这两个最简单的方法它们不过是在屏幕上输出一句问候的话语:
public void GreetPeople(string name) {
// 做某些额外的事情比如初始化之类此处略
EnglishGreeting(name);
}
public void EnglishGreeting(string name) {
ConsoleWriteLine(Morning + name);
}
暂且不管这两个方法有没有什么实际意义GreetPeople用于向某人问好当我们传递代表某人姓名的name参数比如说Jimmy进去的时候在这个方法中将调用EnglishGreeting方法再次传递name参数EnglishGreeting则用于向屏幕输出 Morning Jimmy
现在假设这个程序需要进行全球化哎呀不好了我是中国人我不明白Morning是什么意思怎么办呢?好吧我们再加个中文版的问候方法:
public void ChineseGreeting(string name){
ConsoleWriteLine(早上好 + name);
}
这时候GreetPeople也需要改一改了不然如何判断到底用哪个版本的Greeting问候方法合适呢?在进行这个之前我们最好再定义一个枚举作为判断的依据:
public enum Language{
English Chinese
}
public void GreetPeople(string name Language lang){
//做某些额外的事情比如初始化之类此处略
switch(lang){
case LanguageEnglish:
EnglishGreeting(name);
break;
case LanguageChinese:
ChineseGreeting(name);
break;
}
}
OK尽管这样解决了问题但我不说大家也很容易想到这个解决方案的可扩展性很差如果日后我们需要再添加韩文版日文版就不得不反复修改枚举和GreetPeople()方法以适应新的需求
在考虑新的解决方案之前我们先看看 GreetPeople的方法签名:
public void GreetPeople(string name Language lang)
我们仅看 string name在这里string 是参数类型name 是参数变量当我们赋给name字符串jimmy时它就代表jimmy这个值;当我们赋给它张子阳时它又代表着张子阳这个值然后我们可以在方法体内对这个name进行其他操作哎这简直是废话么刚学程序就知道了
如果你再仔细想想假如GreetPeople()方法可以接受一个参数变量这个变量可以代表另一个方法当我们给这个变量赋值 EnglishGreeting的时候它代表着 EnglsihGreeting() 这个方法;当我们给它赋值ChineseGreeting 的时候它又代表着ChineseGreeting()方法我们将这个参数变量命名为 MakeGreeting那么不是可以如同给name赋值时一样在调用 GreetPeople()方法的时候给这个MakeGreeting 参数也赋上值么(ChineseGreeting或者EnglsihGreeting等)?然后我们在方法体内也可以像使用别的参数一样使用MakeGreeting但是由于MakeGreeting代表着一个方法它的使用方式应该和它被赋的方法(比如ChineseGreeting)是一样的比如:
MakeGreeting(name);
好了有了思路了我们现在就来改改GreetPeople()方法那么它应该是这个样子了:
public void GreetPeople(string name *** MakeGreeting){
MakeGreeting(name);
}
注意到 *** 这个位置通常放置的应该是参数的类型但到目前为止我们仅仅是想到应该有个可以代表方法的参数并按这个思路去改写GreetPeople方法现在就出现了一个大问题:这个代表着方法的MakeGreeting参数应该是什么类型的?
NOTE:这里已不再需要枚举了因为在给MakeGreeting赋值的时候动态地决定使用哪个方法是ChineseGreeting还是 EnglishGreeting而在这个两个方法内部已经对使用morning还是早上好作了区分
聪明的你应该已经想到了现在是委托该出场的时候了但讲述委托之前我们再看看MakeGreeting参数所能代表的 ChineseGreeting()和EnglishGreeting()方法的签名:
public void EnglishGreeting(string name)
public void ChineseGreeting(string name)
如同name可以接受String类型的true和但不能接受bool类型的true和int类型的一样MakeGreeting的 参数类型定义 应该能够确定 MakeGreeting可以代表的方法种类再进一步讲就是MakeGreeting可以代表的方法 的 参数类型和返回类型
于是委托出现了:它定义了MakeGreeting参数所能代表的方法的种类也就是MakeGreeting参数的类型
NOTE:如果上面这句话比较绕口我把它翻译成这样:string 定义了name参数所能代表的值的种类也就是name参数的类型
本例中委托的定义:
public delegate void GreetingDelegate(string name);
可以与上面EnglishGreeting()方法的签名对比一下除了加入了delegate关键字以外其余的是不是完全一样?
现在让我们再次改动GreetPeople()方法如下所示:
public void GreetPeople(string name GreetingDelegate MakeGreeting){
MakeGreeting(name);
}
如你所见委托GreetingDelegate出现的位置与 string相同string是一个类型那么GreetingDelegate应该也是一个类型或者叫类(Class)但是委托的声明方式和类却完全不同这是怎么一回事?实际上委托在编译的时候确实会编译成类因为Delegate是一个类所以在任何可以声明类的地方都可以声明委托更多的内容将在下面讲述现在请看看这个范例的完整代码:
using System;
using SystemCollectionsGeneric;
using SystemText;
namespace Delegate {
//定义委托它定义了可以代表的方法的类型
public delegate void GreetingDelegate(string name);
class Program {
private static void EnglishGreeting(string name) {
ConsoleWriteLine(Morning + name);
}
private static void ChineseGreeting(string name) {
ConsoleWriteLine(早上好 + name);
}
//注意此方法它接受一个GreetingDelegate类型的方法作为参数
private static void GreetPeople(string name GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
static void Main(string[] args) {
GreetPeople(Jimmy Zhang EnglishGreeting);
GreetPeople(张子阳 ChineseGreeting);
ConsoleReadKey();
}
}
}
输出如下:
Morning Jimmy Zhang
早上好 张子阳
我们现在对委托做一个总结:
委托是一个类它定义了方法的类型使得可以将方法当作另一个方法的参数来进行传递这种将方法动态地赋给参数的做法可以避免在程序中大量使用IfElse(Switch)语句同时使得程序具有更好的可扩展性