一、什么是委托
委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变,类似于 C 或 C++ 中函数的指针。委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。
这句话的大意是,以前我们一般都是把数据作为参数传进方法,现在有了委托,小方法也能作为参数传进大方法。这里的“小方法”就是我们要请求“大方法”做的委托。
二、为什么要用委托
举个栗子,我们要写一个问候的方法
public void GreetPeople(string name)
{
EnglishGreeting(name);
}
public void EnglishGreeting(string name)
{
Console.WriteLine("Good Morning, " + name);
}
这个方法可以对不同名字的人问候,但是如果对方是个中国人,就要再加一个中国人的问候方法
public void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
这样我们见到别人,就不只是要看一下他叫什么了,还要选一下我们要用中国人的问候方法还是美国人的问候方法
public enum Language
{
English, Chinese
}
public void GreetPeople(string name, Language lang)
{
switch (lang)
{
case Language.English:
EnglishGreeting(name);
break;
case Language.Chinese:
ChineseGreeting(name);
break;
}
}
OK,尽管这样解决了问题,但如果我们需要再添加韩文版、日文版,就不得不反复修改枚举和 GreetPeople() 方法,以适应新的需求。
在考虑新的解决方案之前,我们先看看 GreetPeople 的方法签名:
public void GreetPeople(string name, Language lang);
如果 GreetPeople 可以接受一个参数变量,这个变量可以代表一个另一个方法:
当我们给这个变量赋值 EnglishGreeting 的时候,它代表着 EnglsihGreeting() 这个方法;
当我们给它赋值 ChineseGreeting 的时候,它又代表着 ChineseGreeting() 方法。
我们将这个参数变量命名为 MakeGreeting ,然后,在方法体里,我们也可以像使用别的参数一样使用 MakeGreeting 。但是,由于 MakeGreeting 代表着一个方法,它的使用方式应该和它被赋的方法(比如 ChineseGreeting)是一样的,比如:
MakeGreeting(name);
所以,它应该是这个样子了:
public void GreetPeople(string name, *** MakeGreeting)
{
MakeGreeting(name);
}
那么问题来了,*** 这个方法参数的类型应该是什么呢?
说明: 这里已不再需要枚举了,因为在给 MakeGreeting 赋值的时候动态地决定使用哪个方法,是 ChineseGreeting 还是 EnglishGreeting,而在这个两个方法内部,已经对使用 “Good Morning” 还是 “早上好 ” 作了区分。
聪明的你应该已经想到了,现在是委托该出场的时候了!但讲述委托之前,我们再看看 MakeGreeting 参数所能代表的 ChineseGreeting() 和 EnglishGreeting() 方法的签名:
public void EnglishGreeting(string name)
public void ChineseGreeting(string name)
需要注意的是,MakeGreeting 的参数类型定义应该能够确定 MakeGreeting 可以代表的方法种类,再进一步讲,就是 MakeGreeting 要能代表的方法的参数类型和返回类型。
于是,委托出现了:它定义了 MakeGreeting 参数所能代表的方法的种类,也就是 MakeGreeting 参数的类型。
本例中委托的定义:
public delegate void GreetingDelegate(string name);
与上面 EnglishGreeting() 方法的签名对比一下,除了加入了delegate 关键字以外,其余的是不是完全一样?现在,让我们再次改动 GreetPeople() 方法,如下所示:
//声明委托
public delegate void GreetingDelegate(string name);
public void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
MakeGreeting(name);
}
所以,完整的代码如下
//声明委托
public delegate void GreetingDelegate(string name);
class Program
{
private static void EnglishGreeting(string name)
{
Console.WriteLine("Good Morning, " + name);
}
private static void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
{
MakeGreeting(name);
}
static void Main(string[] args)
{
GreetPeople("Liker", EnglishGreeting);
GreetPeople("李志中", ChineseGreeting);
Console.ReadLine();
}
}
注意:委托的位置跟类一样。
现在这个代码长得还并不像我们经常见到的委托的格式,进一步的演变请看下一章中 3.2 多播委托 。
总结:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If … Else…(Switch)语句对相同的其他参数进行处理,同时使得程序具有更好的可扩展性。
三、委托的一般用法
3.1 命名方法委托
这种委托就形如上面的栗子,分成三步走:定义声明委托、实例化委托、调用委托。
下面是一个使用委托完成将图书信息按照价格升序排序的应用。
class Book : IComparable<Book>
{
//定义构造方法为图书名称和价格赋值
public Book(string name, double price)
{
Name = name;
Price = price;
}
//定义图书名称属性
public string Name { get; set; }
//定义图价格属性
public double Price { get; set; }
//实现比较器中比较的方法
public int CompareTo(Book other)
{
return (int)(this.Price - other.Price);
}
//重写ToString方法,返回图书名称和价格
public override string ToString()
{
return Name + ":" + Price;
}
//图书信息排序
public static void BookSort(Book[] books)
{
Array.Sort(books);
}
}
class Program
{
//定义对图书信息排序的委托
public delegate void BookDelegate(Book[] books);
static void Main(string[] args)
{
//实例化委托
BookDelegate bookDelegate = new BookDelegate(Book.BookSort);
Book[] book = new Book[3];
book[0] = new Book("计算机应用", 50);
book[1] = new Book("C# 教程", 59);
book[2] = new Book("VS2017应用", 49);
//调用委托
bookDelegate(book);
foreach (Book bk in book)
{
Console.WriteLine(bk);
}
Console.ReadLine();
}
}
注:关于比较器 CompareTo 可以参考 IComparable、IComparer接口:比较两个对象的值 。
因此,使用委托可以用同一个方法一次性处理一组数据。
3.2 多播委托
多播委托 就是一句话做好多事。
比如上面第二章中 中国人和美国人问候 的栗子,如果我们想对同一个人说不同语言的问候,可以进一步修改为:
static void Main(string[] args)
{
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
GreetPeople("Liker", delegate1);
Console.ReadLine();
}
实际上,因为 GreetPeople 没有做其他 EnglishGreeting 和 ChineseGreeting 公共的事情,所以我们可以也可以绕过 GreetPeople 方法,通过委托来直接调用 EnglishGreeting 和 ChineseGreeting :
static void Main(string[] args)
{
GreetingDelegate delegate1;
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
delegate1("Liker");
Console.ReadLine();
}
因此,使用委托可以 将多个方法绑定到同一个委托变量 ,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。当然,也可以解绑方法。
3.3 匿名委托
对于一些 仅需要使用一次 的委托,可以采用匿名委托。
class Program
{
//定义委托
public delegate void AreaDelegate(double length, double width);
static void Main(string[] args)
{
Console.WriteLine("请输入长方形的长:");
double length = double.Parse(Console.ReadLine());
Console.WriteLine("请输入长方形的宽:");
double width = double.Parse(Console.ReadLine());
//定义匿名委托
AreaDelegate areaDelegate = delegate
{
Console.WriteLine("长方形的面积为:" + length * width);
}; //注意:分号!!!
//调用匿名委托列表
areaDelegate(length, width);
}
}
通过上面 3 个步骤即可完成匿名委托的定义和调用,需要注意的是,在定义匿名委托时,代码块结束后要在 {} 后加上分号。
3.4 事件
事件是一种特殊的委托,具体的使用见我的下一篇小文# C#(三)——事件 。