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"); }
 
          ));

}

 

posted @ 2024-12-11 11:55 新西兰程序员 阅读(34) 评论(0) 推荐(0) 编辑

C++中的std::function

摘要: std::function()是C++标准库中的一个通用多态函数包装器, 它可以存储,复制和调用任意可调用目标(函数,lambda表达式,绑定表达式或其他函数对象). std::function占用固定尺寸的内存 . 它允许我们将可调用对象(函数,函数指针,Lambda表达式, std::bind以 阅读全文
posted @ 2024-12-02 16:46 新西兰程序员 阅读(782) 评论(0) 推荐(0) 编辑

C++中的仿函数Functor

摘要: 在C++中,有仿函数Functor的概念,首先要明白,它叫做仿函数,就说明它本身肯定不是一个函数 => 事实上,它是一个类的对象,但是可以像函数一样来进行调用 怎么来理解这句话呢 “仿函数是一个类的对象,但是它可以像函数一样来进行调用”? 是这样的 => 本质是在这个类里面实现一个operator( 阅读全文
posted @ 2024-11-14 16:06 新西兰程序员 阅读(38) 评论(0) 推荐(0) 编辑

C++中的const和constexpr异同比较

摘要: constexpr是C++11引入的关键字,这个关键字用于指明其后面是一个常量,编译器在编译程序时会将其结果计算出来,而无需等到程序运行阶段,这样的优化极大的提高了程序的运行效率 我们知道,C++程序的执行过程,大概需要经历 编译,链接,运行这3个阶段. 这里值得特别关注的是,常量表达式和非常量表达 阅读全文
posted @ 2024-10-23 16:28 新西兰程序员 阅读(19) 评论(0) 推荐(0) 编辑

C++中的左值和右值,以及右值引用

摘要: 在C++中有左值和右值:左值和右值其实是C语言中的概念,但是C标准中并没有给出严格的区分方法。普遍的认为是, 放在=左边的,或者能够取地址的,我们称为左值。只能放在=右边的,或者不能取地址的,称为右值. 但有时候这个判断标准也不一定准确。 C++11中,左值和右值的区分标准: 1. 普通类型的变量, 阅读全文
posted @ 2024-09-05 13:19 新西兰程序员 阅读(53) 评论(0) 推荐(0) 编辑

C++中传递参数是指针类型以及传入参数是指针的指针(**)详解

摘要: C++中传递参数是指针时,在函数内部,其实是会复制一份新的指针,只不过这两个指针指向的是同一块内存地址 首先我们要明白一点,在C++传递参数时,不论是传入指针还是传入值,传入函数后都会在函数内部创建一个副本 => 也就是说,传入前的指针或是值不会变. 但是指针有个点就是,函数内部的这个指针副本,和外 阅读全文
posted @ 2024-08-07 17:08 新西兰程序员 阅读(407) 评论(0) 推荐(0) 编辑

C++中GetTickCount函数学习

摘要: 在看公司代码时,看到使用GetTickCount()函数 阅读全文
posted @ 2024-06-27 17:02 新西兰程序员 阅读(11) 评论(0) 推荐(0) 编辑

C++中以类的成员函数作为Windows callback函数需要设置成static函数

摘要: 在看代码时,发现很多CALLBACK函数,所以仔细研究了一下C++中的CALLBACK函数 首先,我们来理解一下,什么是C++中的CALLBACK函数 => 凡是由你设计,但是由Windows操作系统调用的函数,我们把它统称为CALLBACK函数,这些函数都有一定的类型,以方便配合Windows的调 阅读全文
posted @ 2024-05-29 15:31 新西兰程序员 阅读(23) 评论(0) 推荐(0) 编辑

C#中的System.Security.SecureString学习

摘要: 有一次在公司review代码时,有一个password的字段,原来用的是String类型,有同事提到应该用SecureString比较好 于是我花了点时间了解了一下什么是SecureString, 以及它与String类型的区别 正常的String类型值,在脱离开作用域后,它的值在内存中并不会立即被 阅读全文
posted @ 2024-05-10 15:00 新西兰程序员 阅读(195) 评论(0) 推荐(0) 编辑

C++中的悬挂指针和野指针

摘要: 悬挂指针(Dangling Pointer), 指的是一个指针它指向已经释放的内存或者无效的内存。当指针指向的内存被释放,这个指针仍然保留着指向之前内存地址的数值,但该地址中的数据已经无效或者被其他数据覆盖 比如一个指针 *Ptr, 它最初指向了一块内存,现在这块内存被释放了,或者这块内存被释放后重 阅读全文
posted @ 2024-04-19 09:53 新西兰程序员 阅读(322) 评论(0) 推荐(0) 编辑
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示