C#中的委托详解
今天看到一篇非常好的写C#委托的文章,所以想记录下来
C#中的委托,顾名思义,就是把事情委托给别人来办理,这包括很多种情况,比如我们想在主窗体中想刷新子窗体的控件(修改子窗体的控件的值); 又比如在多线程的情况下,在其他线程中想刷新主线程的控件; 或则在某个窗体中执行另一个窗体的方法
说白了,就是这件事中我直接做完成不了,比如上面的,主窗体中是没有办法直接去刷新子窗体的控件的; 在其他线程中我也没法去刷新主线程的控件. => 这种直接干不了的事情,我就可以考虑使用委托
我们来看一个经典的例子:
public FormMain() { InitializeComponent(); Task.Run(()=> { UpdateMainThreadControl(); }); } private void UpdateMainThreadControl() { this.lblTime.Text = DateTime.Now.ToString("HH:mm:ss"); }
这段代码在执行时,会报错, 报错信息大概如下: “System.InvalidOperationException: 线程间操作无效,从不是创建控件'FormMain'的线程访问它 ” => 这个什么意思呢? 是这样的,我们在方法 UpdateMainThreadControl中的目的是来修改控件lblTime的文本,而这个控件lblTime是在创建FormMain的主线程中创建的.
什么是主线程呢 => 我们知道程序运行之后,会有一个主线程,又叫做 UI线程, 这个主线程通常用于处理用户界面相关的逻辑,如创建和显示窗体,处理用户输入,更新UI等 => 所以在上面的例子中 创建控件 "FormMain"的线程就是主线程, 而在FormMain方法里面,使用Task.Run是从线程池中新调来一个线程,也就是说
执行UpdateMainThreadControl方法的是另一个线程,而不是主线程,所以这里,我们相当于在另一个新的线程中去调用主线程的控件lblTime => 所以报错
所以明白了吧 => 在主线程环境下,我们不能在其他线程中去直接访问主线程的控件 => 那现在我们想要达到这个目的,我们应该怎么办呢 => 可以这么理解,对于Task,Run产生的新线程来说,既然我不能直接访问主线程里面的控件,那我委托别人/第三方来访问总可以吧 => 这里就引入了委托的概念
委托的使用,分5步
1. 声明委托
public delegate void GetTimeDelegate();
声明委托有3点需要注意: 第一点需要关键字 delegate, 第二点需要确定委托的方法是否有参数签名,第三点是确定委托的方法是否有返回值和返回值类型
这里,我们委托的目的是给lblTime控件赋值当前时间,显然不需要参数,也没有返回值
2. 创建委托对象
委托是一种类型,就和类class一样,所以我们可以像给类创建对象一样的来给委托创建委托对象
//创建委托对象 private GetTimeDelegate getCurrentTime;
3. 创建委托方法
委托是个媒介,它自己是不干活的,我们必须还要有一个实际干活的方法
// 创建实际干活的委托方法 private void getTimeMethod() { this.lblTime.Text = DateTime.Now.ToString("HH:mm:ss"); }
4. 委托绑定
现在我们委托对象有了,实际进行干活的委托方法也有了,我们就要把委托对象和实际干活的方法绑定联系起来
// 委托绑定 this.getCurrentTime = this.getTimeMethod;
5.委托调用
如果不涉及到多线程,直接像调用方法一样调用委托对象即可 => 但是上面这个例子涉及到了多线程,所以我们这里最终依然需要主线程来调用委托对象呢 => 那么怎么通过主线程来调用委托对象呢 => Control类中提供了一个Invoke方法,这个方法的含义就是在拥有此控件的基础窗体句柄的线程上执行指定的委托
这个例子中的委托调用代码如下
//多线程方法 private void UpdateMainThreadControl() { //调用委托 this.lblTime.Invoke(getCurrentTime); }
这样,就基于委托解决了跨线程访问的问题.
所以说 => 委托是一种类型,它定义了方法的签名,即方法的参数类型和返回值类型 => C#中的委托,就有点类似于C++中的指针
.Net Framework3.5之后的内置委托
在 .net framework 3.5之后,开始有内置委托,微软自己帮我们写好了底层代码实现的委托 => Action 和 Function
Action委托针对无返回值的情况,具有Action, Action<T>, Action<T1,T2>, Action<T1,T2,T3>......Action<T1,......T16>多达16个参数的形式,其中传入参数都是泛型T,涵盖了几乎所有可能存在的无返回值的委托类型
Func委托针对的是有返回值的情况,具有Func<TResult>, Func<T,TResult>.......Func<T1,T2,T3.....T16,TResult>17种类型重载, 其中T1.......T16为参数,TResult为返回值类型
所以,上面的代码,我们可以进行如下更改 => 上面的委托,没有参数和返回值,所以应该用Action
第一步简化: 我们不再需要声明委托和创建委托对象,用.net中自带的委托Action
private void UpdateMainThreadControl() { //创建并绑定委托 Action action = new Action(getCurrentTime); //调用委托 this.lblTime.Invoke(action); }
再次简化
private void UpdateMainThreadControl() { //创建委托,绑定委托,调用委托 this.lblTime.Invoke(new Action(getCurrentTime)); }
我们还可以用lambar表达式来代替委托方法getCurrentTime
private void UpdateMainThreadControl() { //创建委托,绑定委托,调用委托 this.lblTime.Invoke(new Action( ()=> { this.lblTime.Text = DateTime.Now.ToString("HH:mm:ss"); } )); }