委托那些事儿

一、定义

委托类似于C++的函数指针,但是委托时类型安全的。一个不好听的比喻,生前写了一个遗嘱,死后遗嘱才会公开。委托的意义就是在某个特定的时间做某事,比如点击一个按钮会发生某事,但是你不想修改按钮的代码,可以添加一个方法到委托上,当点击按钮时,会自动执行委托链上的方法。委托增加了复杂性但是也增加了灵活性,方便程序的设计。

二、委托与事件

让委托工作起来需要的步骤:

1. 声明委托类型。

2. 创建委托实例。

3. 为委托实例添加匹配的方法。

4. 调用委托实例。

下面是一个简单的委托例子。

版本一:
class Patent1
{
// 声明委托类型
public delegate void PatentHandler();

// 创建委托实例
public PatentHandler patentHandler = null;

// 时间限制,到100
private int limit;

public Patent1(int limit)
{
this.limit = limit;
}

// 驱动函数
public void Grow(int limit)
{
this.limit += limit;
// 时间到100时,调用委托实例
if (this.limit >= 100 && this.patentHandler !=null)
{
patentHandler();
}
}
}

主函数调用:

static void Main()
{
Patent1 patent = new Patent1(40);

// 为委托实例添加方法,Expire方法是与委托类型匹配的方法
patent.patentHandler = new Patent1.PatentHandler(Expire);

// limit没到100,不调用委托实例
patent.Grow(30);

// 调用委托实例
patent.Grow(30);
}

// 与PatentHandler委托匹配的方法
static void Expire()
{
Console.WriteLine("limit到了100,函数被触发...");
}

从两面的例子中可以看到4个步骤。上例子中委托实例的访问修饰符是public,所以在其他类可以自由调用,于是问题出现了:

假如主函数调用改为

static void Main()
{
Patent1 patent = new Patent1(40);

patent.patentHandler = null;
// 为委托实例添加方法,Expire方法是与委托类型匹配的方法
patent.patentHandler = new Patent1.PatentHandler(Expire);

// 直接调用委托实例
patent.patentHandler.Invoke();

// limit没到100,不调用委托实例
patent.Grow(30);

// 调用委托实例
patent.Grow(30);
}

问题一:

添加了一个语句patent.patentHandler.Invoke();,这样直接就可以调用委托实例了,例子中的情景:专利的年限是100,当满100的时候,才能公开,即调用委托实例。但是客户端调用的时候却可以绕过limit的限制,直接调用委托实例,这就导致了专利年限没满就公开了,大大的不好!

问题二:

添加了一个语句patent.patentHandler = null; 将委托实例的调用列表清空。

版本二:

为了避免上述的两个问题,需要将委托实例的声明为private。但是需要给委托实例注册函数,但是委托实例是私有的,因此需要通过两个函数来帮助委托实例注册和取消注册函数。好比一个私有字段,需要两个函数getter和setter函数来辅助。于是有一个新版本,比第一个版本多了一下代码

// 创建委托实例,声明为private
private PatentHandler patentHandler;

// 为委托实例注册函数
public void Add_Delegate(PatentHandler ph)
{
if (this.patentHandler == null)
{
this.patentHandler = ph;
}
else
{
this.patentHandler += ph;
}
}

// 为委托实例取消注册函数
public void Remove_Delegate(PatentHandler ph)
{
if (this.patentHandler != null)
{
this.patentHandler -= ph;
}
}

主函数调用:

Patent2 patent = new Patent2(40);
patent.Add_Delegate(new Patent2.PatentHandler(Expire));
patent.Add_Delegate(new Patent2.PatentHandler(Expire1));
patent.Remove_Delegate(new Patent2.PatentHandler(Expire));
//patent.patentHandler.Invoke();
patent.Grow(60);

由于委托实例为私有,所以不存在版本一的两种问题。为了解决这个问题每次都得自己写两个辅助函数,是在是太麻烦了。要是有个东西可以把两个函数封装起来就好了。于是语法糖 事件(event)出现了。

版本三:
class Patent3
{
// 声明委托类型
public delegate void PatentHandler();

public event PatentHandler patentEvent;

private int limit;

public Patent3(int limit)
{
this.limit = limit;
}

// 驱动函数
public void Grow(int limit)
{
this.limit += limit;
if (this.limit >= 100 && this.patentEvent != null)
{
patentEvent();
}
}
}

与第一个版本的区别用红色标出。

主函数调用:

Patent3 patent = new Patent3(40);
patent.patentEvent += new Patent3.PatentHandler(Expire);
patent.Grow(60);

功能和版本二一样,但是却简洁了很多。那事件到底是何方神圣。

QQ截图20120614235620

声明一个事件之后,编译器会将事件转换为具有默认实现的add_xxx/remove_xxx的方法和一个私有委托实例字段。私有委托实例字段对类内可见,成对的add/remove方法对类外可见,对应于代码中的+=和-=。

因此:事件不是委托实例,而是成对的对私有委托实例字段进行操作的已实现的add/remove方法,类似于属性的getter和setter方法,为的是封装数据。

三、委托的历史

c#1.0

QQ截图20120615001405

c#2.0

QQ截图20120615001422

在c#2.0中添加了 方法组的隐式转换。

QQ截图20120615001500

添加了匿名函数和委托的协变性/逆变性。

c#3.0

QQ截图20120615002159

lambda表达式。

posted @ 2012-06-15 00:26  zabery  阅读(412)  评论(0编辑  收藏  举报