- 什么是回调函数?
回调函数是一种非常有用的编程机制,.NET Framework通过委托来提供回调函数机制。在.NET Framework中,回调方法的应用非常广泛,例如,登记回调方法获得各种各样的通知,如未处理的异常、窗口状态的变化、菜单项选择、异步操作等等。
回调函数不是由该函数的实现方直接调用,而是在特定的条件或事件发生时,由另一方调用的,用于响应事件。在非托管C/C++中是通过函数指针来实现的,在C#中是通过委托来实现的。
回调函数的意义是把调用者与被调用者分开,调用者(通知者)不关心谁被调用(接收者)。
回调函数相当于非托管C/C++中的回调函数,但后者不是类型安全的。
(可参考:C语言中函数指针和回调函数的详解) - 委托的本质
委托的本质也是一个类,可以定义在类内部,也可在类外部。
委托定义:
public delegate bool MyDelegate(int value);
编译后的完整类:
public class MyDelegate1 : System.MulticastDelegate//所有委托都派生自该类
{
public MyDelegate1(object @object, IntPtr method);
//代码中mydelegate(10),编译后是mydelegate.Invoke(10)
public virtual void Invoke(int 32);
//BeginInvoke和EndInvoke用于异步编程模型(APM)中,实现在线程池中异步的执行委托
public virtual IAsyncResult BeginInvoke(int value, AsyncCallback callback, object @object);
public virtual void EndInvoke(IAsyncResult result);
}
一个委托可以回调多个方法,可通过Delegate.Combine和Delegate.Remove增加和删除回调方法,简化语法是+=和-=。
3. 很多委托的定义实际上相同,.NET Framework提供了几种通用的委托类型:
//Action,不带返回值的委托,最多可带16个参数
public delegate void Action();//非泛型委托
public delegate void Action<T>(T obj);
public delegate void Action<T1,T2>(T1 arg1, T2 arg2);
public delegate void Action<T1,T2,T3>(T1 arg1, T2 arg2, T3 arg3);
public delegate void Action<T1, ...,T16>(T1 arg1,..., T16 arg16);
//Func,带返回值的委托,最多带17个参数
public delegate TResult Func<TResult>(TResult obj);
public delegate TResult Func<T,TResult>(T arg1);
public delegate TResult Func<T1, ...,T16, TResult>(T1 arg1,..., T16 arg16);
建议尽量使用上述委托,可简化编程,减少系统中类型数量。
4. 委托的一些简化语法
1). 可在方法参数中指定回调方法的委托,使用时不必构造该委托对象,直接将回调方法传入即可
//WaitCallback是已知委托
namespace System.Threading
{
public delegate void WaitCallback(object state);
}
public static bool QueueUserWorkItem(WaitCallback callBack)
public void Test()
{
//可直接传入回调方法,不必创建委托对象
ThreadPool.QueueUserWorkItem(DelegateMethod,2);
}
private void DelegateMethod(object o)
{
Console.WriteLine(o.ToString());
}
2). 有时候不需要定义回调方法,通过lambda表达式实现
public void Test()
{
//编译器在看到lambda表达式后,会自动定义一个新的私有方法,称为匿名函数(不同于匿名方法)
//这里o代表传入的参数2,可随意命名
ThreadPool.QueueUserWorkItem((o) =>
{
Console.WriteLine(o.ToString());
}, 2);
//下面这种方式会报错,因为obj为null(obj也是随意命名的)
ThreadPool.QueueUserWorkItem(obj => Console.WriteLine(obj.ToString()));
}
lambda表达式中=>左侧是指定传给lambda表达式的参数名称,右侧是匿名函数主体。下面是一些lambda表达式:
Func<string> f = () => "Tom";//委托不接收任何参数,使用()
Func<int, string> f2 = (int n) => n.ToString();//委托接收一个int参数
Func<int, string> f22 = (n) => n.ToString();//简化版本,由编译器自行推断
Func<int, string> f222 = n => n.ToString();//简化版本,由编译器自行推断
Func<int, int, string> f3 = (int n1, int n2) => (n1 + n2).ToString();//委托接收两个int参数
Func<int, int, string> f31 = (n1, n2) => (n1 + n2).ToString();//简化版本
如果函数主体有多个语句组成,必须用{}将语句封闭。且lambda表达式一般用在某段代码主体只运行一次的的情况中,如果该主体在多处调用,理想方案是单独封装一个方法。