C#委托的前世今生
<h3 data-first-child="">一、前言</h3><p data-pid="GpX_Duxb">大家好!我是付工。</p>
<p data-pid="tlkom58R">十年前,刚开始学C#编程的时候,被委托困扰了很久。</p>
<p data-pid="EyKfMsP6">今天跟大家分享一下关于委托的那些事儿。</p>
<h3>二、委托原理</h3>
<p data-pid="QEEzanFn">什么是委托?</p>
<p data-pid="d7kpZda5">抛开编程,委托是一个汉语词语,指的是把事情托付给别人或别的机构办理。</p>
<p data-pid="1DT2mYqi">为什么会有委托?什么时候使用委托?</p>
<p data-pid="b2fDEYq1">有些事情我们直接干不了,需要找人来帮忙。</p>
<p data-pid="VN4jKkI1">比如:</p>
<p data-pid="6gdyLL0c">我们需要在主窗体中刷新子窗体的控件,</p>
<p data-pid="y13OcXBV">我们需要在多线程中刷新主线程的控件。</p>
<p data-pid="U5GjXdI9">我们需要在某个窗体中执行另一个窗体的方法。</p>
<p data-pid="bwbHwv22">总之,当我们直接完成不了的时候,就可以考虑使用委托,如果可以直接完成,就没有委托什么事了。</p>
<h3>三、委托案例</h3>
<p data-pid="mkuYgvr4">今天,我们结合一个案例来了解委托的前世今生。</p>
<p data-pid="lP1BwJ9H">这个错误应该每个人都遇到过,代码很简单,一运行就报错。</p>
<img width="693" height="361" class="origin_image zh-lightbox-thumb lazy lazyload" data-caption="" data-size="normal" data-rawwidth="693" data-rawheight="361" data-original-token="v2-8757fa0f6a12a4e9d41470806df2d161" data-original="https://pic2.zhimg.com/v2-e62b1dacd2d1f94fbe58580329c57b47_r.jpg" data-actualsrc="https://pic2.zhimg.com/v2-e62b1dacd2d1f94fbe58580329c57b47_1440w.jpg" data-lazy-status="ok" data-src="https://pic2.zhimg.com/80/v2-e62b1dacd2d1f94fbe58580329c57b47_720w.webp">
<p data-pid="h-CGau7f">我们来分析一下错误提示:线程间操作无效:从不是创建控件“FrmMain”线程访问它。</p>
<p data-pid="Jjd3kBrk">这里的线程间意味着涉及两个线程,一个就是我们开的线程,另一个就是主线程。</p>
<p data-pid="cGcuMdi0">从不是创建控件“FrmMain”线程,指的是什么线程呢?</p>
<p data-pid="KDFpsQL9">我们知道程序运行之后,就会有一个主线程,又叫UI线程,通常用于处理用户界面相关的逻辑,如创建和显示窗体、处理用户输入、更新UI等。</p>
<p data-pid="8eYR5cgy">所以,创建控件"FrmMain"的线程就是主线程。</p>
<p data-pid="4bqfu1Ev">不是创建控件“FrmMain”线程指的就是我们开的那个线程。</p>
<p data-pid="JBYpCgEX">因此这个错误的意思就是不能在多线程里直接访问主线程的控件。</p>
<p data-pid="rteYesab">这个我们做牛马的应该都能理解,比如你花了半个月写了一个很牛的程序,你的同事想要"窃取",你是否愿意?</p>
<div class="GifPlayer css-o0k2vi" data-size="small" data-za-detail-view-path-module="GifItem"><img alt="动图封面" width="200" class="ztext-gif lazyload" data-thumbnail="https://pica.zhimg.com/v2-5b4bfd22f80051c23ee2028ab050f9e2_b.jpg" data-size="small" data-src="https://pica.zhimg.com/v2-5b4bfd22f80051c23ee2028ab050f9e2_b.jpg">
<div class="GifPlayer-icon css-d39tw7"> </div>
</div>
<p data-pid="DipCilAD">主线程辛辛苦苦创建了控件,多线程想直接给它赋值,主线程自然也不愿意,一怒之下,就给出了一个错误警告。</p>
<img width="503" height="217" class="origin_image zh-lightbox-thumb lazy lazyload" data-caption="" data-size="normal" data-rawwidth="503" data-rawheight="217" data-original-token="v2-bb0a5913f1571ac6a11cf237ebd87850" data-original="https://pic3.zhimg.com/v2-2810195f3564b2878a682283510d51d0_r.jpg" data-actualsrc="https://pic3.zhimg.com/v2-2810195f3564b2878a682283510d51d0_1440w.jpg" data-lazy-status="ok" data-src="https://pic3.zhimg.com/80/v2-2810195f3564b2878a682283510d51d0_720w.webp">
<h3>四、解决方案</h3>
<p data-pid="oh5V-fO2">我们回到这个问题上:你花了半个月写了一个很牛的程序,你的同事想要"窃取",你不愿意给,他怎么办呢?</p>
<p data-pid="aXz8RB2E">于是,聪明的他开始搬救兵,找到了你的领导,表示他有一个类似的项目,利润很高,客户很急,需要借用你的程序参考一下。</p>
<p data-pid="pSJsGGi_">于是,你的领导跟你画了一张饼,毋庸置疑,你同意了。</p>
<p data-pid="SoBx2Dek">你的同事就是那个线程,而你就是主线程,你的领导就是委托。</p>
<p data-pid="bR4c7FzV">我们再回到这个错误上来,既然直接不能访问主线程的控件,那么就采用"委托"来实现。</p>
<p data-pid="IiF4TdYp">想要使用委托,必然要先学会委托。</p>
<p data-pid="3L7E32lX">在C#中,委托是一种类型,它定义了方法的签名,即方法的参数类型和返回值类型。</p>
<div class="GifPlayer css-o0k2vi" data-size="small" data-za-detail-view-path-module="GifItem"><img alt="动图封面" width="197" class="ztext-gif lazyload" data-thumbnail="https://pic1.zhimg.com/v2-45f837586d427dbc50afb2c56fb2aa6e_b.jpg" data-size="small" data-src="https://pic1.zhimg.com/v2-45f837586d427dbc50afb2c56fb2aa6e_b.jpg">
<div class="GifPlayer-icon css-d39tw7"> </div>
</div>
<p data-pid="0lmvRs_x">这句话如果没看明白,就不用管它了。</p>
<p data-pid="xKAFS3Nu">我们先来看看如何使用委托,这里总结了委托的五步法:</p>
<p data-pid="VWwzfiOP">1、声明委托</p>
<p data-pid="ax-Efqww">声明委托需要根据最终执行方法来确定参数与返回值类型,然后根据参数和返回值来声明。</p>
<p data-pid="lXOoz3hp">我们目的是给lbl_Time控件赋值当前时间,因此参数和返回值均为空。</p>
<p data-pid="UyjvUwYV">声明委托代码如下</p>
<div class="highlight">
<pre><code class="language-csharp"><span class="c1">//【1】声明委托
<span class="c1"><span class="k">public <span class="k">delegate <span class="k">void <span class="n">SetTimeDelegate<span class="p">();
</span></span></span></span></span></span></span></code></pre>
</div>
<p data-pid="mZE-j3PJ">2、创建委托对象</p>
<p data-pid="byJrXLv7">委托是一种类型,就像类class一样,我们都知道如果要创建某个类的对象的写法,那么创建委托对象是一样的。</p>
<p data-pid="MhYcT-OF">创建委托对象代码如下:</p>
<div class="highlight">
<pre><code class="language-csharp"><span class="c1">//【2】创建委托对象
<span class="c1"><span class="k">private <span class="n">SetTimeDelegate <span class="n">setTime<span class="p">;
</span></span></span></span></span></span></code></pre>
</div>
<p data-pid="65FFIq2i">3、创建委托方法</p>
<p data-pid="f56e7X8Y">委托对象就像领导一样,它是不干活的,最终干活的还得是下面的牛马。</p>
<p data-pid="RUA_lp_-">因此我们需要编写一个最终干活的方法,我们这个活很简单,所以委托方法也很简单。</p>
<p data-pid="nMFluxot">创建委托方法代码如下:</p>
<div class="highlight">
<pre><code class="language-csharp"><span class="c1">//【3】创建委托方法
<span class="c1"><span class="k">private <span class="k">void <span class="n">setTimeMethod<span class="p">()
<span class="p">{
<span class="k">this<span class="p">.<span class="n">lbl_Time<span class="p">.<span class="n">Text <span class="p">= <span class="n">DateTime<span class="p">.<span class="n">Now<span class="p">.<span class="n">ToString<span class="p">(<span class="s">"HH:mm:ss"<span class="p">);
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p data-pid="J4ROJWx6">4、委托绑定</p>
<p data-pid="l9ShXGwQ">领导有了,牛马有了,如何将这两者联系起来呢?</p>
<p data-pid="Ds3XUqLO">我们需要进行关系绑定,这就需要进行委托绑定。</p>
<p data-pid="4WoA719-">委托绑定代码如下:</p>
<div class="highlight">
<pre><code class="language-csharp"><span class="c1">//【4】委托绑定
<span class="c1"><span class="k">this<span class="p">.<span class="n">setTime <span class="p">= <span class="k">this<span class="p">.<span class="n">setTimeMethod<span class="p">;
</span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p data-pid="LQ9n8MfR">5、委托调用</p>
<p data-pid="_R7h6pBm">如果不涉及多线程,直接就像调用方法一样调用委托对象即可。</p>
<p data-pid="QSm7t6tb">但是这里涉及到了多线程,也就是我们这里最终仍然需要主线程来调用。</p>
<p data-pid="ZzFHOJnp">怎么通过主线程来调用这个委托对象呢?</p>
<img width="556" height="182" class="origin_image zh-lightbox-thumb lazy lazyload" data-caption="" data-size="normal" data-rawwidth="556" data-rawheight="182" data-original-token="v2-d9877bc513c6bf992990fc8250525010" data-original="https://pic1.zhimg.com/v2-b17fb361442b7af7e1f246332eb38130_r.jpg" data-actualsrc="https://pic1.zhimg.com/v2-b17fb361442b7af7e1f246332eb38130_1440w.jpg" data-lazy-status="ok" data-src="https://pic1.zhimg.com/80/v2-b17fb361442b7af7e1f246332eb38130_720w.webp">
<p data-pid="kMi-JaEF">Control类中提供了一个Invoke方法,这个方法的含义是在拥有此控件的基础窗体句柄的线程上执行指定的委托。</p>
<p data-pid="kRvlddkl">委托调用的代码如下:</p>
<div class="highlight">
<pre><code class="language-text">//多线程方法
private void TaskMethod()
{
//【5】调用委托
this.lbl_Time.Invoke(setTime);
}</code></pre>
</div>
<p data-pid="r-z3jpx7">这样就基于委托解决了跨线程访问的问题。</p>
<p data-pid="dNOMk45g">我们运行一下程序,效果如下:</p>
<img width="337" height="199" class="content_image lazy lazyload" data-caption="" data-size="small" data-rawwidth="337" data-rawheight="199" data-original-token="v2-e778011efe092b7a4682b8b1d6c88bfa" data-actualsrc="https://pic1.zhimg.com/v2-ffdf8497e4993d91319c444143ec2fac_1440w.jpg" data-lazy-status="ok" data-src="https://pic1.zhimg.com/80/v2-ffdf8497e4993d91319c444143ec2fac_720w.webp">
<p data-pid="HC7ZOggt">这时候,我们在看这句话,是不是就豁然开朗了呢?</p>
<p data-pid="FDgLFQJX">委托是一种类型,它定义了方法的签名,即方法的参数类型和返回值类型。</p>
<p data-pid="uQ8Z7TPh">如果我们接触过C++编程,委托类似于C++中的指针。</p>
<div class="GifPlayer css-o0k2vi" data-size="small" data-za-detail-view-path-module="GifItem"><img alt="动图封面" width="210" class="ztext-gif lazyload" data-thumbnail="https://pic4.zhimg.com/v2-8da5f5a9628d898f669e4cc6d1e295d7_b.jpg" data-size="small" data-src="https://pic4.zhimg.com/v2-8da5f5a9628d898f669e4cc6d1e295d7_b.jpg">
<div class="GifPlayer-icon css-d39tw7"> </div>
</div>
<h3>五、委托今生</h3>
<p data-pid="H1dVSZ1E">.Net Framework3.5之后开始有了Action和Func,Action和Func是内置委托,也叫系统委托,就是微软的工程师帮我们在底层写好了委托声明,这样我们就不需要声明委托。</p>
<p data-pid="4tlptrRj">Action委托针对无返回值情况,具有Action、Action<T>、Action<T1,T2>、Action<T1,T2,T3>……Action<T1,……T16>多达16个参数的形式,其中传入参数均采用泛型T,涵盖了几乎所有可能存在的无返回值的委托类型。</p>
<p data-pid="fbQkvxif">Func委托针对有返回值情况,具有Func<TResult>、Func<T,Tresult>……Func<T1,T2,T3……,Tresult>17种类型重载,T1……T16为参数,Tresult为返回类型。</p>
<p data-pid="PqUwsZ9h">于是我们开始简化我们的代码:</p>
<p data-pid="OLeJJnv4">第一步简化:使用Action,不需要声明委托,创建的时候直接绑定</p>
<div class="highlight">
<pre><code class="language-csharp"> <span class="c1">//多线程方法
<span class="c1"><span class="k">private <span class="k">void <span class="n">TaskMethod<span class="p">()
<span class="p">{
<span class="c1">//创建并绑定
<span class="c1"> <span class="n">Action <span class="n">action <span class="p">= <span class="k">new <span class="n">Action<span class="p">(<span class="n">setTimeMethod<span class="p">);
<span class="c1">//调用委托
<span class="c1"> <span class="k">this<span class="p">.<span class="n">lbl_Time<span class="p">.<span class="n">Invoke<span class="p">(<span class="n">action<span class="p">);
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p data-pid="HtSRi40z">第二步简化:action对象只使用一次,直接调用即可</p>
<div class="highlight">
<pre><code class="language-csharp"> <span class="c1">//多线程方法
<span class="c1"> <span class="k">private <span class="k">void <span class="n">TaskMethod<span class="p">()
<span class="p">{
<span class="c1">//创建委托、绑定委托、调用委托
<span class="c1"> <span class="k">this<span class="p">.<span class="n">lbl_Time<span class="p">.<span class="n">Invoke<span class="p">(<span class="k">new <span class="n">Action<span class="p">(<span class="n">setTimeMethod<span class="p">));
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p data-pid="jM-Ltb5z">第三步简化:使用Lambda表达式替换委托方法</p>
<div class="highlight">
<pre><code class="language-csharp"><span class="c1">//多线程方法
<span class="c1"><span class="k">private <span class="k">void <span class="n">TaskMethod<span class="p">()
<span class="p">{
<span class="c1">//创建委托、委托方法、绑定委托、调用委托
<span class="c1"> <span class="k">this<span class="p">.<span class="n">lbl_Time<span class="p">.<span class="n">Invoke<span class="p">(<span class="k">new <span class="n">Action<span class="p">(()=>
<span class="p">{
<span class="k">this<span class="p">.<span class="n">lbl_Time<span class="p">.<span class="n">Text <span class="p">= <span class="n">DateTime<span class="p">.<span class="n">Now<span class="p">.<span class="n">ToString<span class="p">(<span class="s">"HH:mm:ss"<span class="p">);
<span class="p">}));
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p data-pid="GoIRU2m_">这个最终简化的代码是不是非常熟悉呢?</p>
<div class="GifPlayer css-o0k2vi" data-size="small" data-za-detail-view-path-module="GifItem"><img alt="动图封面" width="240" class="ztext-gif lazyload" data-thumbnail="https://pic3.zhimg.com/v2-d2b5c9db207d4baf319d5a81289f2eb2_b.jpg" data-size="small" data-src="https://pic3.zhimg.com/v2-d2b5c9db207d4baf319d5a81289f2eb2_b.jpg"></div><br><br>
来源:https://www.cnblogs.com/xbdedu/p/18594690
頁:
[1]