首先,应该明确,CLR提供委托机制就是提供了回调函数机制
在C#中,回调函数这个机制更多的被用在了事件中,经常被用到的EventHandler的本质就是一个委托。
(注意委托类型和委托实例的不同)
(1)声明委托类型 (2)必须有一个对应委托类型的方法且包含了要执行的代码 (3)必须创建一个委托实例 (4)必须调用(invoke)委托实例
// 表明了一个返回值为void,并且入参为一个string类型的方法
delegate void StringProcessor(string str);
// 或者使用更加通用的命令方式来体现委托的声明
delegate void ProgressOneStringParamaterVoidFunc(string str);
// 或者一个可以通过C#协变性的特点,接受更大范围内的回调函数
delegate object ProgressAnyType(stream anystream);
StringProcessor实际上是一个类型,它有方法,可以创建它的实例,并将引用传递给实例
// 一个符合委托声明的函数实现;
static void PrintString(string str)
{
Console.WriteLine("PrintString:" + str);
}
class ClassChapter2Instance
{
public void PrintStringInstance(string str)
{
Console.WriteLine("PrintStringInstance:" + str);
}
}
public string GetFileStreamReturnString(FileStream fs)
{
// string ret;
// ……
return ret;
}
static void Main(string[] args)
{
// 创建委托类型的实例;
StringProcessor printstring;
// 创建委托实例,使用!!!静态方法!!!;
printstring = new StringProcessor(Program.PrintString);
// 创建委托类型的实例;这个对象被称为操作的目标
StringProcessor printstringinstance;
// 创建委托实例,使用!!!实例方法!!!;
ClassChapter2Instance C2 = new ClassChapter2Instance();
printstringinstance = new StringProcessor(C2.PrintStringInstance);
}
printstring("Delegate~printstring");
// 调用这个委托实例
printstringinstance("Delegate~printstringinstance");
}
Lambda表达式的简化形式:即省略了括号,花括号,return关键字
delegate(显示类型参数列表) { 实现代码 }
Lambda表达式:
标准形式:(显示类型 参数列表) => { 实现代码 }
简化形式:参数名 => 表达式
其中,=>是C#3.0新增的,告诉编译器正在使用Lambda表达式,更加明确的理解,可以将这个Lambda表达式理解为:参数变成了代码的结果
例子A:最简单的做到了简化代码量的作用
delegate void StringProcessor(string str);
StringProcessor Func;
// 以下这三个是等价的:
Func = delegate(string TextArg) { return; };
Func = (string arg) => { return; };
Func = TextArg => return;
C#的委托,本质上就是一种类型,所以使用它的时候需要用到new关键字。
所以,实例化出来的委托提供了如下几个平时能经常用到的方法供我们使用:
有一个这样的场景:有一个后台线程一直在监听某一个地址上的消息,一旦监听到它所需要的消息,就会发送一个通知消息,给所有在它这里注册的对象,暂且称这个消息为Trap(这个是SNMP网络协议中的叫法)。那么对于这个“观察者“来说,观察者类的设计如下:
public delegate void UpdateTrap(string stringlist);
public class GetMessage
{
public static Thread WaitforTrap = new Thread(WaitTrap); // 执行Trap接收的线程;
private static List<UpdateTrap> TrapNodifyList = new List<UpdateTrap>(); // 观察者列表;
private static volatile bool WaitTrapRunstate = true; // 是否接收Trap的标志位;
private static Socket socket;
///
/// 设置观察者列表;
///
/// 需要被添加的观察者
public static void SetNodify(UpdateTrap obj)
{
TrapNodifyList.Add(obj);
// 如果是第一次设置SetNodify,则启动Trap监听线程;
if (TrapNodifyList.Count == 1)
{
TrapMessage.WaitforTrap.Start();
}
}
///
/// 统一调用观察者的Update方法;
///
///
static public void Nodify(List<string> Ret)
{
foreach (UpdateTrap update in TrapNodifyList)
{
update(Ret);
}
}
///
/// 接收Trap信息;
///
static private void WaitTrap()
{
// 建立一个Socket实例,接收所有网口的IP地址,并绑定***端口;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(ep);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 0);
string Ret = "";
// 一直等待Socket返回结果
while (WaitTrapRunstate)
{
byte[] data = new byte[16 * 1024];
int length = -1;
IPEndPoint peer = new IPEndPoint(IPAddress.Any, 0);
EndPoint inep = (EndPoint)peer;
try
{
inlen = socket.ReceiveFrom(data, ref inep);
// ~~~解析~~~
Ret = "解析结果";
}
catch (Exception ex)
{
Console.WriteLine("Exception {0}", ex.Message);
inlen = -1;
}
// 如果inlen大于0则证明接收到Trap;
if (inlen > 0)
{
// 通知在观察者处注册了的实例;
Nodify(Ret);
}
else
{
if (inlen == 0)
Console.WriteLine("Zero length packet received.");
}
}
Console.WriteLine("Trap waiting thread: terminating gracefully.");
}
public static void RequestStop()
{
if(TrapNodifyList.Count != 0)
{
socket.Dispose(); // 释放线程;
WaitTrapRunstate = false; // 释放监听套接字;
return;
}
else
{
return;
}
}
}
在其他对象中注册通知消息:
class ProceedWhenGetMessage
{
ProceedWhenGetMessage()
{
TrapMessage.SetNodify(this.proceed);
}
private void proceed(string Message)
{
// Do something
}
}
为了不让程序员再费劲单独自定义委托,造成不必要的混淆,微软在.NET环境下提供了多种不同类型的公用委托,首先总结如下:
委托名称 | 说明 | 场景举例 |
---|---|---|
Func<……> | 自由功能,包含一个或多个入参,返回一个制定类型的委托 | Func |
Predicate | 是否符合条件,包含一个入参,返回一个bool类型 | 一般用于判断入参是否符合某个条件,例如List.FindAll(Predicate) |
Action | 执行动作,包含一个入参,没有返回值,即直接利用入参执行某项操作 | 一般用于不需要返回结果直接执行动作场景,例如List.ForEach(Action) |
说明: 表示一种方法,这个方法具有一个或多个参数,并且这个参数返回由TResult指定的数据类型,在.NET3.5之后中,有多个预设的泛型Func委托类型,入参个数最大的可以达到16个。Func系列委托的声明如下:
public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
Func
也就是说,如果能获取到一个int类型参数,并想要获取string类型的返回结果,直接使用Func
原型:
public delegate bool Predicate<in T>(T obj)
说明: 表示一种方法,这种方法定义一种条件,并返回是否满足了这个条件
举一个例子:
有的时候,经常需要判断一个List中是否存在某些元素,就可以用到FindAll方法,而FindAll方法接受的就是一个Predicate委托:
List<string> strlist = new List<string>();
strlist.Add("1");
List<string> findret = strlist.FindAll(new Predicate<string>(delegate (string instring) {
if (instring == "1")
return true;
else
return false;
}));
其中FindAll的函数原型如下:
// 摘要:
// 检索与指定谓词定义的条件匹配的所有元素。
//
// 参数:
// match:
// System.Predicate`1 委托,用于定义要搜索的元素应满足的条件。
//
// 返回结果:
// 如果找到一个 System.Collections.Generic.List`1,其中所有元素均与指定谓词定义的条件匹配,则为该数组;否则为一个空 System.Collections.Generic.List`1。
//
// 异常:
// T:System.ArgumentNullException:
// match 为 null。
public List<T> FindAll(Predicate<T> match);
原型:
public delegate void Action();
说明: 表示一种仅需要需要执行系列动作的回调函数,没有返回值
举一个例子:
在C#开发中,有非常多的场景会用到Action,比如在WPF中,想要用另外一个线程往主线程的控件中异步写入内容,就需要用到一个Action委托来处理:
// 注:TrapText是一个Text的WPF控件,并应用了匿名函数
this.TrapText.Dispatcher.Invoke(
new Action(
delegate
{
foreach (string content in TrapContent)
{
TrapText.Text += content + "\r\n";
}
}
)
);
其中Invoke的原型是这样的:
// 摘要:
// 执行指定 System.Action 线程上同步 System.Windows.Threading.Dispatcher 与相关联。
//
// 参数:
// callback:
// 要通过调度程序调用的委托。
public void Invoke(Action callback);