在C#中,委托(delegate)是一种引用类型,在其他语言中,与委托最接近的是函数指针,但委托不仅存储对方法入口点的引用,还存储对用于调用方法的对象实例的引用。简单的讲委托(delegate)是一种类型安全的函数指针.


        仅仅看它的概念,可能还是很模糊,我们来举例子由浅入深地说明一下。(强烈提醒:注意代码中的一些关键注释。代码完全可以复制下来直接运行。)


        需求:《收费系统》计费标准:会员1元/小时;临时用户:1.5元/小时。


//我们先来看看不使用委托,该如何实现

usingSystem;

namespace不使用委托
{
class Program
{
static void Main(string[] args)
{
Charging(3, User.NormalUser);
Charging(3, User.VipUser);
}

//枚举
public enum User { VipUser, NormalUser}

//结账
public static void Charging(int hour,User user)
{
switch (user)
{
case User.VipUser:
VipUser(hour);
break;
case User.NormalUser:
NormalUser(hour);
break;
}
}
//结账方式
public static void VipUser(int hour)
{
Console.WriteLine("您是会员,使用了{0}个小时,需要付费{1}元", hour, 1 * hour);
}

public static void NormalUser(int hour)
{
Console.WriteLine("您是临时用户,使用了{0}个小时,需要付费{1}元", hour, 1.5 *hour);
}
}
}


//运行结果:   您是临时用户,使用了3个小时,需要付费4.5元

                         您是会员,使用了3个小时,需要付费3元


//显然,尽管这样解决了问题,但我不说,大家也很容易想到,这个解决方案的可扩展性很差,如果日后我们需要再添加其他结账方式,就不得不反复修改枚举和Charging()方法,以适应新的需求。


//接下来用委托实现一把


usingSystem;

namespace使用委托
{

public delegate void ChargingDelegate(inthour);
class Program
{

static void Main(string[] args)
{
ChargingDelegate delegate1,delegate2;
delegate1 = VipUser;
delegate2 = NormalUser;

Charging(3, delegate1);
Charging(3,delegate2);


//先按会员结账,再按临时用户结账(呵呵,虽然有点不符合逻辑)
//这里主要是想说明:可以将多个方法赋给同一个委托,或者
//叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。
delegate1 += delegate2;
Charging(2, delegate1);
//既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,这个语法是“-=”

}


public static void Charging(int hour,ChargingDelegate charge)
{
charge(hour);
}

public static void VipUser(int hour)
{
Console.WriteLine("您是会员,使用了{0}个小时,需要付费{1}元", hour, 1 * hour);
}

public static void NormalUser(int hour)
{
Console.WriteLine("您是临时用户,使用了{0}个小时,需要付费{1}元", hour, 1.5 *hour);
}
}
}



//运行结果:   您是会员,使用了3个小时,需要付费3元

                         您是普通用户,使用了3个小时,需要付费4.5元

                         您是会员,使用了3个小时,需要付费3元

                         您是普通用户,使用了3个小时,需要付费4.5元



//这样,我们看到,利用委托屏蔽掉了switch语句。


        小结:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。


//虽然我们做到了可扩展,但是考虑到面向对象,要做到对象的封装,我们可以把上述代码做以下改进。

usingSystem;

namespace使用委托
{
class Program
{
//更加简洁的客户端
static void Main(string[] args)
{

ChargingManager CM=newChargingManager();
CM.delegate1=VipUser;
CM.delegate1+=NormalUser;
CM.Charging(3);

}
//结账方式
public static void VipUser(int hour)
{
Console.WriteLine("您是会员,使用了{0}个小时,需要付费{1}元", hour, 1 * hour);
}

public static void NormalUser(int hour)
{
Console.WriteLine("您是临时用户,使用了{0}个小时,需要付费{1}元", hour, 1.5 *hour);
}
}


public delegate void ChargingDelegate(inthour);

//既然可以声明委托类型的变量(在上例中是delegate1),我们可以将这个变量封装到 GreetManager类中。
class ChargingManager
{
public ChargingDelegate delegate1;

public void Charging(int hour)
{
if (delegate1!=null)
delegate1(hour);
}
}
}



//运行结果:您是会员,使用了3个小时,需要付费3元

                      您是普通用户,使用了3个小时,需要付费4.5元



//这里有一条语句很奇怪(publicChargingDelegate delegate1;),并不是所有的字段都应该声明成public,合适的做法是应该public的时候public,应该private的时候private。

但是这里,我们不能改成private,因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见。

不禁想到,如果delegate1不是一个委托类型,而是一个string或者int类型,我们会怎么做?自然是使用属性对字段进行封装了。


          这个时候,我们就不得不用到事件!


我们再来优化代码:

usingSystem;

namespace使用委托
{
class Program
{
static void Main(string[] args)
{

ChargingManager CM=newChargingManager();
//CM.MakeCharge = VipUser;
//“=”,是赋值语法(直接访问)会报错,只能放在+=或-=的右边。说明 MakeCharge被声明为私有的了。
CM.MakeCharge+=VipUser;
CM.MakeCharge+=NormalUser;
CM.Charging(3);

}
//结账方式
public static void VipUser(int hour)
{
Console.WriteLine("您是会员,使用了{0}个小时,需要付费{1}元", hour, 1 * hour);
}

public static void NormalUser(int hour)
{
Console.WriteLine("您是临时用户,使用了{0}个小时,需要付费{1}元", hour, 1.5 *hour);
}
}


public delegate void ChargingDelegate(inthour);

class ChargingManager
{
//声明事件MakeCharge,他的类型是ChargingDelegate
public event ChargingDelegate MakeCharge;
//虽然是public,但因为多了一个event。MakeCharge变为私有的了,客户端中得到证明

public void Charging(int hour)
{
MakeCharge(hour);
}
}
}



我们看到,在客户端中,使用 “=”赋值语法(直接访问)会报错,只能放在+=或-=的右边。说明 MakeCharge被声明为私有的了。


难道事件仅仅是为了使共有变私有嘛?当然不是,详见以下实例。


需求:《收费系统》上机卡充值,在数据库中,一方面更改该卡的余额,另一方面储存充值记录。


代码实现:

usingSystem;

namespace委托与事件
{
class Program
{
static void Main(string[] args)
{
Recharge RC = newRecharge("100");
UpdateBalance UB = newUpdateBalance("100");
ChargeRecord CR = newChargeRecord("100");

//将UpdateBalance的Update方法通过实例化委托EventHandler注册到Recharge的事件MakeRecharge 中。
RC.MakeRecharge+=newEventHandler(UB.Update);

//将ChargeRecord的Record方法通过实例化委托EventHandler注册到Recharge的事件MakeRecharge 中。
RC.MakeRecharge += newEventHandler(CR.Record);

RC.Charge();

}
}

//声明委托EventHandler
public delegate void EventHandler();

//充值
class Recharge
{
private string strMoney;
public Recharge(string money)
{
this.strMoney = money;
}

//声明事件MakeRecharge,其事件类型为委托EventHandler
public event EventHandler MakeRecharge;

public void Charge()
{
Console.WriteLine("现在充值{0}元", strMoney);

//当执行Charge方法时,如果MakeRecharge中有对象注册事件,则执行MakeRecharge
if (MakeRecharge != null)
{
MakeRecharge();
}
}
}

//更改余额
class UpdateBalance
{
private string strMoney;
public UpdateBalance(string money)
{
this.strMoney = money;
}

public void Update()
{
Console.WriteLine("充值成功,余额为(原余额)+{0}元", strMoney);
}
}

//储存充值记录
class ChargeRecord
{
private string strMoney;
public ChargeRecord(string money)
{
this.strMoney = money;
}

public void Record()
{
Console.WriteLine("储存充值记录成功,本次充值{0}元", strMoney);
}
}
}



注:这里我们用到了一种重要的设计模式,即:观察者模式.可以认为是先有观察者模式,才有的委托事件技术.观察者设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。它是一种松耦合的设计模式。


综上,我们由浅到深得研究了委托与事件。以下总结了几点,需要大家注意:

1、委托是一种引用方法的类型,一旦为委托分配了方法,委托将于该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。

2、委托也可以看成是一种数据类型,可以用于定义量,但它是一种特殊的数据类型,它可以接受的数值只能是一个函数,更确切的说,委托的变量可以接受一个函数的地址。

3、一个委托可以搭载多个方法,所有方法被依次唤起,更重要的是,它可以使委托对象所搭载的方法并不一定属于同一个类。

4、委托对象所搭载的方法必须具有相同的参数列表和返回值类型。