1. 委托的定义
委托声明定义了一种类型,它用一组特定的参数以及返回类型来封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一个实例和该实例上的一个方法。如果您有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。
委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。
下面的示例声明了一个名为 Del 的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。
public delegate void Del (string message);
2. 使用委托
下面举个简单的例子
public class PrinStr
{
//声明一个输出的方法
public void CallPrint(string InPut)
{
Console.WriteLine(InPut);
}
}
class ListMembers
{
//声明委托类型
public delegate void PrintHandler(string str);
public static void Main (String[] args)
{
PrinStr clsPS = new PrinStr();
//clsPS的方法实例化委托
PrintHandler myHandler = new PrintHandler(clsPS.CallPrint);
//调用委托的方法
myHandler("Hellp World!");
}
使用委托的注意点:
1.创建委托所使用的方法必须和委托声明相一致(参数列表、返回值都一致)
2.利用 +=、-=来进行委托的链接、取消链接或直接使用Delegate.Combine和Delegate.Remove方法来实现
3.可以使用MulticastDelegate的实例方法GetInvocationList()来获取委托链中所有的委托
4.不能撰写包含 out 参数的委托
可能这时会有疑问了,使用委托跟直接调用有什么区别?
那我们再来看一个例子
有一个公司(场景),你是老板,手下有主管和员工,作为老板你会指派(委托)主管管理员工的工作,如果某个员工玩游戏,则让某个主管从该员工的薪水里扣去500元钱。
这就是现实中的委托。
而在写程序中,假设程序员就是老板,有两个类分别为主管和员工,而主管小王和员工小张就是两个类的对象实例。员工类有一个方法:玩游戏,同时就有一个玩游戏的事件,他一玩游戏就会激发这个事件。而主管类就是负责处理该事件的,他负责把玩游戏的员工的薪水扣除500。
2.1 首先,我们来看看在非委托的情况下比较常见的一种设计方式(当然这不是唯一的方式,也不是最好的方式,但是很常见):
using System;
namespace TestDelegate
{
public class 场景
{
[STAThread]
public static void Main (string[] args)
{
Console.WriteLine("场景开始了.");
// 生成主管类的对象实例小王
主管 小王= new 主管();
// 生成员工类的对象实例小张,指定他的主管
员工 小张= new 员工(小王);
Console.WriteLine("该员工本有的薪水:" + 小张.薪水.ToString());
// 员工开始玩游戏
小张.玩游戏();
Console.WriteLine("现在该员工还剩下:" + 小张.薪水.ToString());
Console.WriteLine("场景结束");
Console.ReadLine();
}
}
// 负责扣钱的人----主管
public class 主管
{
public 主管()
{
Console.WriteLine("生成主管");
}
public void 扣薪水(员工 employee)
{
Console.WriteLine("主管:好小子,上班时间胆敢玩游戏");
Console.WriteLine("主管:看看你小子有多少薪水");
Console.WriteLine("开始扣薪水...");
System.Threading.Thread.Sleep(1000);
employee.薪水= employee.薪水- 500;
Console.WriteLine("扣薪水执行完毕.");
}
}
// 如果玩游戏,则会引发事件
public class 员工
{
// 保存员工的薪水
private int m_Money;
// 保存该员工的主管
private 主管 m_Manager;
public 员工(主管 manager)
{
Console.WriteLine("生成员工.");
m_Manager = manager; // 通过构造函数,初始化员工的主管。
m_Money = 1000; // 通过构造函数,初始化员工的薪水。
}
public int 薪水// 此属性可以操作员工的薪水。
{
get
{
return m_Money;
}
set
{
m_Money = value;
}
}
public void 玩游戏()
{
Console.WriteLine("员工开始玩游戏了..");
Console.WriteLine("员工:CS真好玩,哈哈哈!我玩...");
System.Threading.Thread.Sleep(1000);
m_Manager.扣薪水(this);
}
}
}
这种方法所带来的问题: 员工类和主管类的耦合性太高
1、 在客户程序里必须先创建了主管类之后才能生成员工类,如果在不需要主管类对象而只需员工类对象的地方,为了创建所需的员工类对象实例,你也不得不去先创建一个主管类的对象实例;
2、 如果场景剧本(即客户程序需求)发生了变化
(1)、现在要让一个新的角色(一个新的类),如保安,来代替主管,负责在员工玩游戏时扣员工薪水,那么我们不得不去修改员工类,或许还需要修改主管类;
(2)、如果场景剧本增加新的需求,要求员工在玩游戏后,不但要扣薪水,还要在绩效上扣分,那么我们也不得不修改员工类。
2.2 使用委托实现
下面例子在控制台应用程序运行成功
namespace TestDelegate
{
//声明委托
public delegate void PlayGameHandler(object sender,EventArgs e);
//定义主管类
public class 主管
{
public 主管()
{
Console.WriteLine("生成主管!");
}
public void 扣薪水(object sender,EventArgs e)
{
Console.WriteLine("主管:好小子,上班时间胆敢玩游戏");
Console.WriteLine("主管:看看你小子有多少薪水");
员工 employee = (员工)sender;
Console.WriteLine("开始扣薪水...");
System.Threading.Thread.Sleep(1000);
employee.薪水= employee.薪水- 500;
Console.WriteLine("扣薪水执行完毕.");
}
}
public class 员工
{
//定义一个事件,表示员工正在玩游戏
public event PlayGameHandler playgame;
//员工的薪水
private int m_Money;
public 员工()
{
Console.WriteLine("生成员工!");
m_Money = 1000;
}
//定义薪水的属性
public int 薪水
{
get { return m_Money; }
set { m_Money = value; }
}
public void 玩游戏()
{
Console.WriteLine("员工开始玩游戏了..");
Console.WriteLine("员工:CS真好玩,哈哈哈!我玩...");
//线程休眠秒
System.Threading.Thread.Sleep(1000);
EventArgs e = new EventArgs();
OnPlayGame(e);
}
public virtual void OnPlayGame(EventArgs e)
{
if (playgame != null)
{
playgame(this, e);
}
}
}
public class Test
{
public static void Main (string[] args)
{
Console.WriteLine("场景开始!");
// 生成主管类的对象实例小王
主管 小王= new 主管();
// 生成员工类的对象实例小张
员工 小张= new 员工();
// 设下委托,指定监视
小张.playgame += new PlayGameHandler(小王.扣薪水);
Console.WriteLine("该员工本有的薪水:" + 小张.薪水.ToString());
// 员工开始玩游戏
小张.玩游戏();
Console.WriteLine("现在该员工还剩下:" + 小张.薪水.ToString());
Console.WriteLine("场景结束");
}
}
}
对于前面提出的问题:
1) 解决了主管类和员工类的必然联系,可以单独创建员工对象实例,而不用管是否有主管类对象实例的存在
2) 在客户程序变化时,我们只需修改客户程序,如上面的例子
a) 保安 小李 = new 保安();
小张.PlayGame += new PlayGameHandler(小李. 扣薪水);
可实现由保安来负责扣薪水的需求变化,而不用动员工类
b)小张.PlayGame += new PlayGameHandler(某某. 扣绩效分);
这个“某某”可以是主管,也可以是其他新的角色(新的类),只需要在“某某”对应的类里定义扣绩效分的动作即可,而不用动员工类。
当前,不使用委托我们依然可以实现解耦的类,只不过要多写很多类和接口,但这样不仅是增加了代码的复杂性和逻辑性,维护起来也很麻烦。