惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
博客园 - 聂微东
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog RSS Feed
小众软件
小众软件
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
B
Blog
H
Help Net Security
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
罗磊的独立博客
月光博客
月光博客
博客园 - 司徒正美

博客园 - 新西兰程序员

C++ 构造函数中的初始化参数列表initializer list C++中的线程thread,以及C++11中的std::atomic学习 Leetcode56 Merge Intervals 合并区间 -- C++实现 C++中的内存对齐 C++中的static关键字使用以及全局变量 C++17中的结构化绑定Structured Binding C++中的函数参数是指针类型, 是按值传递 C++中异常处理机制中的栈展开stack unwinding C++实现链表反转 Linux中的性能分析工具 perf 来分析C++性能 C++17中新建一个类时,编译器默认生成的类成员函数 QT中的元对象系统 Leetcode 114 - 二叉树展开为链表 Leetcode65 有效数字 - 判断给定的字符串是否是一个有效数字 LettCode2289-Steps to Make Array Non-descreasing C++中的std::function C++中的仿函数Functor C++中的const和constexpr异同比较 C++中的左值和右值,以及右值引用,移动语义 C++中传递参数是指针类型以及传入参数是指针的指针(**)详解 C++中GetTickCount函数学习 C++中以类的成员函数作为Windows callback函数需要设置成static函数 C#中的System.Security.SecureString学习 C++中的悬挂指针和野指针 C++中四种不同的对象生存方式(in stack, in heap, global, local static) C#中使用Parallel类来进行多线程并发编程 String类型转LPCTSTR -----理解C++中的字符串类型转换 C++中的虚函数和虚函数表 C++中基类指针指向派生类对象 数据结构 - 栈的学习
C#中的委托详解
新西兰程序员 · 2024-12-11 · via 博客园 - 新西兰程序员

今天看到一篇非常好的写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"); }
 
          ));

}