C#委托(delegate、Action、Func、predicate)和事件
<h2>一、前言</h2><p>刚开始工作的时候,觉得委托和事件有些神秘,而当你理解他们之后,也觉得好像没有想象中的那么难。在项目中运用委托和事件,你会发现他非常棒,这篇博文算是自己对委托和事件的一次梳理和总结。</p>
<h2>二、委托</h2>
<p>C#中的委托,相当于C++中的指针函数,但委托是面向对象的,是安全的,是一个特殊的类,当然他也是引用类型,委托传递的是对方法的引用。</p>
<h3>2.1、delegate</h3>
<p>声明委托就必须使用关键字“delegate”,委托是先声明,后实例化。至少0个参数,至多32个参数</p>
<p>格式如下所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">delegate</span> <span style="color: rgba(0, 0, 255, 1)">string</span> GetAsString();</pre>
</div>
<p>委托是一个类,所以他的实例化跟类的实例化一样,只是他总是接受一个将委托方法作为参数的构造函数。调用委托方法就有两种方式,如下所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">int</span> i = 10;
var method = <span style="color: rgba(0, 0, 255, 1)">new</span> GetAsString(i.ToString);
<span style="color: rgba(0, 128, 0, 1)">//调用方法一</span>
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">method方法{method()}</span>");
<span style="color: rgba(0, 128, 0, 1)">//调用方法二</span>
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">method.Invoke方法{method.Invoke()}</span>");
</pre>
</div>
<p>运行结果:</p>
<p><img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215154165-1811299246.png" alt="image" width="394" height="152" border="0"></p>
<h3>2.2、Action</h3>
<p>Action是无返回值的泛型委托,可以接受0个至16个传入参数</p>
<p>Action 表示无参,无返回值的委托</p>
<p>Action<int,string> 表示有传入参数int,string无返回值的委托</p>
<p>前面我们【Log4Net 日志记录的实现】中,就使用了Action。如:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Debug(<span style="color: rgba(0, 0, 255, 1)">string</span> message, Action RegistedProperties)
{
RegistedProperties();
log.Debug(message);
}</pre>
</div>
<p>调用方式为:</p>
<div class="cnblogs_code">
<pre>PFTLog.Debug("<span style="color: rgba(139, 0, 0, 1)">测试扩展字段</span>", () => {
LogicalThreadContext.Properties["<span style="color: rgba(139, 0, 0, 1)">LogType</span>"] = "<span style="color: rgba(139, 0, 0, 1)">扩展字段内容</span>";
});
</pre>
</div>
<p>在运行中,直接运行Action中的内容即可。</p>
<h3>2.3、Func</h3>
<p>Func是有返回值的泛型委托,可以接受0个至16个传入参数</p>
<p>Func<int> 表示无参,返回值为int的委托</p>
<p>Func<object,string,int> 表示传入参数为object, string 返回值为int的委托</p>
<p><span class="wlWriterPreserve"></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">decimal</span> GetTotal(Func<<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">decimal</span>> func, <span style="color: rgba(0, 0, 255, 1)">int</span> a, <span style="color: rgba(0, 0, 255, 1)">int</span> b)
{
<span style="color: rgba(0, 0, 255, 1)">return</span> func(a, b);
}
</pre>
</div>
<p>调用方式</p>
<div class="cnblogs_code">
<pre>var total = GetTotal((a, b) => { <span style="color: rgba(0, 0, 255, 1)">return</span> (<span style="color: rgba(0, 0, 255, 1)">decimal</span>)a + b; }, 1, 2);
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">结果为{total}</span>");
</pre>
</div>
<p>运行结果</p>
<p><img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215154772-1314456354.png" alt="image" width="331" height="128" border="0"></p>
<h3>2.4、predicate</h3>
<p>predicate 是返回bool型的泛型委托,只能接受一个传入参数</p>
<p>predicate<int> 表示传入参数为int 返回bool的委托</p>
<p>定义一个方法:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> FindPoints(<span style="color: rgba(0, 0, 255, 1)">int</span> a)
{
<span style="color: rgba(0, 0, 255, 1)">return</span> a >= 60;
}</pre>
</div>
<p>定义Predicate委托</p>
<div class="cnblogs_code">
<pre> Predicate<<span style="color: rgba(0, 0, 255, 1)">int</span>> predicate = FindPoints;</pre>
</div>
<p>调用</p>
<div class="cnblogs_code">
<pre>var points = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">int</span>[] {
10,
50,
60,
80,
100 };
var result = Array.FindAll(points, predicate);
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">结果为{string.Join(</span>";"<span style="color: rgba(139, 0, 0, 1)">, result)}</span>");</pre>
</div>
<p>运行结果</p>
<p><img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215155383-332989877.png" alt="image" width="383" height="151" border="0"></p>
<h3>2.5、多播委托</h3>
<p>前面的只包含了一个方法的调用,委托可以包含多个方法,这种委托就叫做多播委托。多播委托利用“+=”和“-+”两种运算符进行添加和删除委托。</p>
<p>先定义两个方法</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> MultiplyByTwo(<span style="color: rgba(0, 0, 255, 1)">double</span> v)
{
<span style="color: rgba(0, 0, 255, 1)">double</span> result = v * 2;
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">传值:{v};MultiplyByTwo结果为{result}</span>");
}
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Square(<span style="color: rgba(0, 0, 255, 1)">double</span> v)
{
<span style="color: rgba(0, 0, 255, 1)">double</span> result = v * v;
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">传值:{v};Square结果为{result}</span>");
}</pre>
</div>
<p>然后调用</p>
<div class="cnblogs_code">
<pre>Action<<span style="color: rgba(0, 0, 255, 1)">double</span>> operations = MultiplyByTwo;
operations(1);
operations += Square;
operations(2);
</pre>
</div>
<p>运行结果:</p>
<p><img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215156016-2063472943.png" alt="image" width="378" height="144" border="0"></p>
<h2>三、事件</h2>
<p>事件是基于委托,为委托提供一种发布/订阅机制,声明事件需要使用event关键字。</p>
<p>发布者(Publisher):一个事件的发行者,也称作是发送者(sender),其实就是个对象,这个对象会自行维护本身的状态信息,当本身状态信息变动时,便触发一个事件,并通知说有的事件订阅者;</p>
<p>订阅者(Subscriber):对事件感兴趣的对象,也称为Receiver,可以注册感兴趣的事件,在事件发行者触发一个事件后,会自动执行这段代码</p>
<p>是不是看到sender,就有种很熟悉的感觉!!!先不忙着急,我们先看下事件的声明和使用</p>
<p>有这样一个应用场景,如果系统有异常,需要及时的通知管理员。那么需要在我们的日志记录里面添加通知管理员的功能,但是问题来了,该怎么通知管理员呢?至少现在无法知道。所以我们就需要在使用到事件。</p>
<p>添加代码如下,如果不知道日志功能的可以参考【Log4Net 日志记录的实现】</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//声明一个通知的委托</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">delegate</span> <span style="color: rgba(0, 0, 255, 1)">void</span> NoticeEventHander(<span style="color: rgba(0, 0, 255, 1)">string</span> message);
<span style="color: rgba(0, 128, 0, 1)">//在委托的机制下我们建立以个通知事件</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">event</span> NoticeEventHander OnNotice;</pre>
</div>
<p>调用方式</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Debug(<span style="color: rgba(0, 0, 255, 1)">string</span> message, Action RegistedProperties)
{
RegistedProperties();
log.Debug(message);
<span style="color: rgba(0, 128, 0, 1)">//执行通知</span>
OnNotice?.Invoke($"<span style="color: rgba(139, 0, 0, 1)">系统异常,请及时处理,异常信息:{message}</span>");
}
</pre>
</div>
<p>在引用场景的代码,先定义一个通知管理员的方法(这里我们直接Console.WriteLine出来)</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Notice(<span style="color: rgba(0, 0, 255, 1)">string</span> message)
{
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">通知内容为{message}</span>");
}</pre>
</div>
<p>先注册,然后触发异常消息</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//注册方式一</span>
PFTLog.OnNotice += Notice;
<span style="color: rgba(0, 128, 0, 1)">//注册方式二</span>
<span style="color: rgba(0, 128, 0, 1)">//PFTLog.OnNotice += new PFTLog.NoticeEventHander(Notice);</span>
PFTLog.Debug("<span style="color: rgba(139, 0, 0, 1)">测试扩展字段</span>", () => {
LogicalThreadContext.Properties["<span style="color: rgba(139, 0, 0, 1)">LogType</span>"] = "<span style="color: rgba(139, 0, 0, 1)">扩展字段内容</span>";
});
</pre>
</div>
<p>运行结果</p>
<p><img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215156644-1401392335.png" alt="image" width="539" height="135" border="0"></p>
<p>这里面我只需要定义好发布者,你可以以任何方式订阅,是不是很非常简单。</p>
<p>弄明白了上面的事件,我们在来说说.Net经常出现的object sender和EventArgs e</p>
<p>.Net Framework的编码规范:</p>
<p>一、委托类型的名称都应该以EventHandler结束</p>
<p>二、委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)</p>
<p>三、事件的命名为 委托去掉 EventHandler之后剩余的部分</p>
<p>四、继承自EventArgs的类型应该以EventArgs结尾</p>
<p>现在我们以一个新书发布的自定义事件为例</p>
<p>创建对应的类文件:<img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215157283-965145803.png" alt="image" width="368" height="83" border="0"></p>
<p>事件者发布代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> BookInfoEventArgs : EventArgs
{
<span style="color: rgba(0, 0, 255, 1)">public</span> BookInfoEventArgs(<span style="color: rgba(0, 0, 255, 1)">string</span> bookName)
{
BookName = bookName;
}
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> BookName { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; }
}
</pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> BookDealer
{
<span style="color: rgba(0, 128, 0, 1)">//泛型委托,定义了两个参数,一个是object sender,第二个是泛型 TEventArgs 的e</span>
<span style="color: rgba(0, 128, 0, 1)">//简化了如下的定义</span>
<span style="color: rgba(0, 128, 0, 1)">//public delegate void NewBookInfoEventHandler(object sender, BookInfoEventArgs e);</span>
<span style="color: rgba(0, 128, 0, 1)">//public event NewBookInfoEventHandler NewBookInfo;</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">event</span> EventHandler<BookInfoEventArgs> NewBookInfo;
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> NewBook(<span style="color: rgba(0, 0, 255, 1)">string</span> bookName)
{
RaiseNewBookInfo(bookName);
}
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> RaiseNewBookInfo(<span style="color: rgba(0, 0, 255, 1)">string</span> bookName)
{
NewBookInfo?.Invoke(<span style="color: rgba(0, 0, 255, 1)">this</span>, <span style="color: rgba(0, 0, 255, 1)">new</span> BookInfoEventArgs(bookName));
}
}
</pre>
</div>
<p>事件订阅者</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> Consumer
{
<span style="color: rgba(0, 0, 255, 1)">public</span> Consumer(<span style="color: rgba(0, 0, 255, 1)">string</span> name)
{
Name = name;
}
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; }
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> NewBookHere(<span style="color: rgba(0, 0, 255, 1)">object</span> sender, BookInfoEventArgs e)
{
Console.WriteLine($"<span style="color: rgba(139, 0, 0, 1)">用户:{Name},收到书名为:{ e.BookName}</span>");
}
}
</pre>
</div>
<p>事件订阅和取消订阅</p>
<div class="cnblogs_code">
<pre>var dealer = <span style="color: rgba(0, 0, 255, 1)">new</span> BookDealer();
var consumer1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Consumer("<span style="color: rgba(139, 0, 0, 1)">用户A</span>");
dealer.NewBookInfo += consumer1.NewBookHere;
dealer.NewBook("<span style="color: rgba(139, 0, 0, 1)">book112</span>");
var consumer2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Consumer("<span style="color: rgba(139, 0, 0, 1)">用户B</span>");
dealer.NewBookInfo += consumer2.NewBookHere;
dealer.NewBook("<span style="color: rgba(139, 0, 0, 1)">book_abc</span>");
dealer.NewBookInfo -= consumer1.NewBookHere;
dealer.NewBook("<span style="color: rgba(139, 0, 0, 1)">book_all</span>");
</pre>
</div>
<p>运行结果</p>
<p><img style="display: inline; background-image: none" title="image" src="https://img2018.cnblogs.com/blog/1764554/201909/1764554-20190914215157959-1080392542.png" alt="image" width="767" height="258" border="0"></p>
<p>经过这个例子,我们可以知道Object sender参数代表的是事件发布者本身,而EventArgs e 也就是监视对象了。深入理解之后,是不是觉得也没有想象中的那么难了。</p>
<h2>四、总结</h2>
<p>这里我们讲了委托和事件,在.Net开发中使用委托和事件,可以减少依赖性和层的耦合,开发出具有更高的重用性的组件。</p>
</div>
<div id="MySignature" role="contentinfo">
<div style="font-size: 13px; border: 1px dashed rgb(45, 161, 45); padding: 10px 15px; background-color: rgb(248, 248, 248)">
<label style="font-weight: bold">
如果您觉得阅读本文对您有帮助,请点一下右下角推荐”按钮,博主在此感谢!另外您也可以选择【<strong>关注我</strong>】,可以很方便找到我!</label><br>
感谢您花时间阅读此篇文章,如果您觉得看了这篇文章之后心情还比较高兴,可以打赏一下,请博主喝上一杯咖啡,让博主继续码字……<br>
本文版权归作者和博客园共有,来源网址:https://www.cnblogs.com/snailblog 欢迎各位转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接
</div><br><br>
来源:https://www.cnblogs.com/snailblog/p/11520438.html
頁:
[1]