早先在CSDN发表的《delegate,event, lambda,Func,Action》文章重新整理一下
delegate
delegate标示了函数回调的规范,能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。
using System;
namespace TestCSharp
{
class Animation
{
//无返回无参数的delegate(委托、代理)
public delegate void PlayEndHandler();
//定义event(事件),类型为上面定义的PlayEndHandler委托
public event PlayEndHandler PlayEndCallBack;
//有返回有参数
public delegate string ActionHandler(int type,string message);
public event ActionHandler ActionCallBack;
public Animation()
{
//可以是成员方法
PlayEndHandler handler = Move;
handler();
this.PlayEndCallBack = Move;
this.PlayEndCallBack();
//也可是静态方法
PlayEndHandler runHandler = Run;
runHandler();
ActionHandler aHandler = Jump;
aHandler(190,"You Jump!");
//设置的方法必须和定义一致
this.ActionCallBack = Jump;
Console.WriteLine(this.ActionCallBack(10001,"Jumping"));
}
private void Move()
{
Console.WriteLine("Move");
}
private static void Run()
{
Console.WriteLine("Run");
}
private string Jump(int type,string message)
{
string content = String.Format("type:{0},message:{1}",type,message);
Console.WriteLine(content);
return content;
}
}
}
event
加入event关键字,编译器会自动针对事件生成一个私有的字段(与此事件相关的委托),以及两个访问器方法,即add访问器方法以及remove访问器方法,用于对事件的Subscribe注册及Unsubscribe注销(对事件使用+=和使用-=操作时就是调用的这两个方法)。实际上声明一个委托类型的字段也可以实现这些功能。 之所以采用event而不直接采用委托,还是为了封装。可以设想一下,如果直接采用公共的委托字段,类型外部就可以对此字段进行直接的操作了,比如将其直接赋值为null。 而使用event关键字就可以保证对事件的操作仅限于add访问器方法以及remove访问器方法(即只能使用+=及-=)
using System;
namespace TestCSharp
{
class Animation
{
//无返回无参数的delegate(委托)
public delegate void PlayEndHandler();
//定义event(事件),类型为上面定义的PlayEndHandler委托
public event PlayEndHandler PlayEndCallBack;
public Animation()
{
this.PlayEndCallBack += Move;
this.PlayEndCallBack += Run;
this.PlayEndCallBack();
this.PlayEndCallBack -= Run;
this.PlayEndCallBack -= Move;
if (this.PlayEndCallBack != null)
{
this.PlayEndCallBack();
}
PlayEndHandler d1 = Move;
PlayEndHandler d2 = Run;
this.PlayEndCallBack = d1 + d2;
this.PlayEndCallBack -= Run;
this.PlayEndCallBack();
//Move,Run,Move
}
private void Move()
{
Console.WriteLine("Move");
}
private static void Run()
{
Console.WriteLine("Run");
}
}
}
如果多次注册同一个事件处理函数时,触发时处理函数是否也会多次触发?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp
{
class Animation
{
public delegate void PlayHandler();
public event PlayHandler Callback;
public Animation()
{
this.Callback += Move;
this.Callback += Move;
this.Callback += Move;
this.Callback();
}
public void Move()
{
Console.WriteLine("Move");
}
public void Run()
{
Console.WriteLine("Run");
}
public void Jump()
{
Console.WriteLine("Jump");
}
}
}
//输出:Move Move Move
可以注册多次同一个函数,触发时会如上所示执行多次.
如果注册了一个事件处理函数,却执行了两次或多次”注销事件“,是否会报错?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp
{
class Animation
{
public delegate void PlayHandler();
public event PlayHandler Callback;
public Animation()
{
this.Callback += Move;
this.Callback -= Move;
this.Callback -= Move;
this.Callback();
}
public void Move()
{
Console.WriteLine("Move");
}
public void Run()
{
Console.WriteLine("Run");
}
public void Jump()
{
Console.WriteLine("Jump");
}
}
}
注册一次,可以注销多次,但是要注意触发时要检查是否为空,做如下修改后避免如上图的空指针异常
if (this.Callback != null)
{
this.Callback();
}
如果不手动注销事件函数,系统会帮我们回收吗?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp
{
class EventSource
{
public delegate void PlayHandler();
public event PlayHandler Callback;
public void Excute()
{
if (this.Callback != null)
{
this.Callback();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp
{
class Listener
{
public Listener(EventSource source)
{
source.Callback += Run;
}
private void Run()
{
Console.WriteLine("Run");
}
~Listener()
{
Console.WriteLine("Listener 销毁");
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
namespace TestCSharp
{
class MainClass
{
public static void Main(string[] args)
{
EventSource s = new EventSource();
Listener l = new Listener(s);
s.Excute();
l = null;
Gc();
s.Excute();
s = null;
Gc();
}
public static void Gc()
{
Console.WriteLine("GC 开始");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("GC 结束");
}
}
}
注意第二次调用s.Excute()前,已将listener赋为null,并执行Gc,但是仍然输出Run。直到将source赋为null时,listener才被回收掉。 下面我们手工注销事件,看看系统能不能自动回收?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestCSharp
{
class Listener
{
public Listener(EventSource source)
{
source.Callback += Run;
}
//添加一个手工注销方法
public void DeregisterEvent(EventSource source)
{
source.Callback -= Run;
}
private void Run()
{
Console.WriteLine("Run");
}
~Listener()
{
Console.WriteLine("Listener 销毁");
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
namespace TestCSharp
{
class MainClass
{
public static void Main(string[] args)
{
EventSource s = new EventSource();
Listener l = new Listener(s);
s.Excute();
//手工调用注销
l.DeregisterEvent(s);
l = null;
Gc();
s.Excute();
s = null;
Gc();
}
public static void Gc()
{
Console.WriteLine("GC 开始");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine("GC 结束");
}
}
}
这次我们可以看到run执行一次,listener也早早的被回收了。
更多系统回收资料参见Weak Events in C#
lambda
using System;
namespace TestCSharp
{
class Animation
{
//无返回无参数的delegate(委托)
public delegate void PlayEndHandler();
//定义event(事件),类型为上面定义的PlayEndHandler委托
public event PlayEndHandler PlayEndCallBack;
public delegate void RunEndHandler(string message);
public event RunEndHandler RunCallBack;
public Animation()
{
//委托回调
this.PlayEndCallBack += Move;
//匿名委托
this.PlayEndCallBack += delegate { Console.WriteLine("Jump"); };
//匿名lambda
this.PlayEndCallBack += () => Console.WriteLine("Fly");
this.PlayEndCallBack();
this.RunCallBack += delegate(string name) { Console.WriteLine("Jump"); };
this.RunCallBack += (string name) => Console.WriteLine("Fly");
this.RunCallBack("cab");
}
private void Move()
{
Console.WriteLine("Move");
}
private void Run(string message)
{
Console.WriteLine("Run");
}
}
}
如何认定两个事件处理函数是一样的? 如果是匿名函数呢?
using System.Text;
namespace TestCSharp
{
class Animation
{
public delegate void PlayHandler();
public event PlayHandler Callback;
public Animation()
{
this.Callback += ()=> Console.WriteLine("Jump");
//Event Unsubscription Via Anonymous Delegate
this.Callback -= () => Console.WriteLine("Jump");
}
}
}
Resharper给出提示Event Unsubscription Via Anonymous Delegate,看起来一致的匿名函数,实际上方法签名是不一样,所以不能如上代码所示取消订阅一个匿名方法。如下代码给出如何取消订阅匿名方法的解决方案。
using System.Text;
namespace TestCSharp
{
class Animation
{
public delegate void PlayHandler();
public event PlayHandler Callback;
public Animation()
{
PlayHandler func = () => Console.WriteLine("Jump");
this.Callback += func;
this.Callback -= func;
this.Callback += func;
this.Callback();
}
}
}
更多取消匿名事件的资料参见官方文档
Func
Func:有输入参数,还有返回值的泛型委托
using System;
namespace TestCSharp
{
class Animation
{
//无参数,返回值为string
public Func<string> callback;
//参数为bool,int,返回值为string
public Func<bool,int,string> playCallback1;
//有多种方法重载
public Func<int,string> playCallback2;
public Animation()
{
this.callback = Move;
Console.WriteLine(this.callback());
this.playCallback1 = Run;
Console.WriteLine(this.playCallback1(false,10001));
this.playCallback2 = Run;
Console.WriteLine(this.playCallback2(10002));
}
private string Move()
{
return "Move";
}
private string Run(bool isRun,int type)
{
return "Run1";
}
private string Run( int type)
{
return "Run2";
}
}
}
Action
Action:只有输入参数,无返回值的泛型委托
using System;
namespace TestCSharp
{
class Animation
{
//无参数,返回值为string
public Action<string> callback;
//参数为bool,int,返回值为string
public Action<bool, int> playCallback1;
//有多种方法重载
public Action playCallback2;
public Animation()
{
this.callback = Move;
this.callback("Hi");
this.playCallback1 = Run;
this.playCallback1(false, 10001);
this.playCallback2 = Jump;
this.playCallback2();
}
private void Move(string message)
{
Console.WriteLine("Move");
}
private void Run(bool isRun,int type)
{
Console.WriteLine("Run");
}
private void Jump()
{
Console.WriteLine("Jump");
}
}
}
predicate
predicate:返回bool型的泛型委托, predicate
using System;
namespace TestCSharp
{
class Animation
{
public Predicate<int> Callback;
public Animation()
{
this.Callback = Move;
Console.WriteLine(this.Callback(1));
}
private bool Move(int type)
{
Console.WriteLine("Move");
return true;
}
}
}