龙空技术网

C#核心-委托和匿名函数揭秘1

爱国程序员 621

前言:

现时兄弟们对“net reflector中文”可能比较看重,你们都需要剖析一些“net reflector中文”的相关知识。那么小编在网络上搜集了一些有关“net reflector中文””的相关内容,希望朋友们能喜欢,各位老铁们一起来了解一下吧!

我想你肯定知道委托和匿名函数。一般文章都会将委托和事件放在一起讨论,也有很多文章介绍匿名函数的用法。我想真诚地向你提一个问题,你自己写的代码里面有多少关于委托和匿名函数的代码,如果有的话,希望你写在评论里面。接下来我为大家讲解一下为什么我要将委托和匿名函数放在一起讨论。

委托是指"指向方法的引用"。是指"可以将一组实例方法或者静态方法绑定到一个委托类型的实例上,执行委托相当于执行了这个绑定的这组方法"。我想大家也非常明白这句话的意思和用法。

我想从委托的历史过程循序渐进地讲解一下,请看下图。

图1

图2

请看图1和图2,描述了C#1.0里面委托的用法,具体的用法我这里就不说了。我想说一点的是C#1.0里面委托必须写明创建实例化的过程,也就是

"new StringProcessor(xx)"

new实例化这句话必不可少,而且参数是一个方法,方法必须是实例化方法或者是静态方法。我想说的是上面这个例子并没有实战意义,我们首先会问C#1.0,那些微软的老外为什么要搞一个委托出来?我只能说他们高瞻远瞩,想到了委托的广泛运用的可行性。而事实上C#1.0委托并没有广泛运用,而只是常用到了winform里面事件上面。

那我们就先扯开话题聊一聊事件和委托的关系。面向面试编程的我们,对这个话题也非常明白,面试也是经常问的问题。那你给出的答案是什么呢?是"事件是特殊的委托"?或者是"事件是委托类型的实例"。我们先讲一下winform里面的事件应用。

我们知道winform里面添加一个界面,会有一个designer.cs,这个是系统生成的代码,而我们只需要在控件对应的事件绑定的方法写我们的逻辑就可以,触发控件就会触发这个绑定的方法代码。设计和绑定过程都是系统生成的代码。请查看下图

图3

图4

上面的例子很好理解,写法上,事件是类的成员,是在委托实例之前加了一个event关键字,它首先不是一个属性,所以不能通过get,set这样的用法。事件就是事件 ,他是和属性、字段、方法一样平起平坐的类的成员,而委托不是类的成员。事件也可以和委托一样绑定多个方法,而且触发事件就可以触发方法组。

设计事件和委托的侧重点是不一样的。事件其实是实现了"发布/订阅"设计模式的实现方式之一。"发布/订阅"设计模式重点是发布者和订阅者之间的信息交互,发布者发布一个信息渠道,订阅者订阅这个信息渠道,等到发布者发布信息的时候,订阅者可以通过这个渠道实时接收到信息。比如上面这个例子,按钮点击事件,发布者是按钮,订阅的是接收类,这个接收类可能就是当前窗体,等到按钮点击触发,当前窗体就可以通过绑定的方法接收信息。或者说生活中我们订阅公众号一样,我们关注公众号,发布者是公众号的主体公司或者个人,我们是订阅者。关注之后,等到公众号发布文章,我们就能接收到。这里面会有非常清楚的订阅者和发布者,订阅者不需要知道发布者什么时候发布信息,但是我们知道肯定是发布者发布信息,我们才会收到信息。

那和委托什么关系呢,只能说C#的事件实现"发布/订阅"模式的技术手段是委托而已。我们分析一下代码。请看下图。

图5

这是一个控制台应用程序,我们定义了一个Person类,定义了一个事件b,还有触发事件的方法DoJob,我们在Main方法里面实例化了一个Person类并且订阅了事件b,订阅的方法是Person_b。这里面发布者是Person类,订阅者是控制台主程序,虽然Main方法里面调用了persona.DoJob(),好像是控制台主程序是触发了事件,但是其实最终还是Person内部触发了事件,你们看DoJob()这个方法。你无法直接在Main方法里面这样执行"person.b()",会报错的,只能在Person内部执行,更加说明那句话,"发布/订阅"模式,触发发布动作的只能是发布者本身。接下来我们更深入的看一下event关键字在C#里面到底是什么回事?

首先我想和你说另外一个话题,就是C#代码执行的过程,这里我想长话短说。首先C#编译器会将C#代码编译成IL(中间语言),元数据。dll大家都知道,他里面大体就是这两样东西。我想只介绍IL语言,因为这个话题应该另外起一个文章介绍才行。

IL到底是个啥,有什么用呢?原因就是因为.NET平台不止C#一种语言,还有vb,f#等很多种语言,你开心的话你也可以搞一个语言出来。那么这些语言语法会有不同,微软就想要通过一个中间语言统一这些语言,让这些语言通过各自编译器生成一样的代码,也就是IL中间语言。那么后面处理这些代码就统一处理了。也就是微软说我支持你发明一个新语言,我不管你发明的语言是中文还是汉语我都不在乎,只要你的编译器生成的代码是IL代码,那么就可以在我.NET平台运行。执行的过程会用到JIT编译器,会将IL代码生成机器语言,也就是汇编语言,这样cpu就可以执行了。

为什么我要扯开话题聊一下C#代码执行过程,就是因为在我们学习过程中会发现C#一直在升级,目前.NET6 已经到了C# 10,各种语法糖让我们更加方便地使用,但是也增加了复杂度(都是一些什么玩意每天升级让人一直得去学习)。但是我想告诉大家的是,IL语言改变不大,IL归根到底还是高级语言,有类,对象,方法,字段,委托,事件等,也就是说不管C#再怎么升级,都是编译器变的魔术,最终生成的IL语言和前面版本的C#是差不多的。当然异步函数也是编译器变的一种魔术。

既然C#各个版本生成的IL语言是类似或者说是一样的, 那么我们直接把上面的代码降级到最原始版本。可以通.NET Reflector工具降级。得到以下代码

图6

通过.NET Reflector工具降级到最低版本,也就是最原始的C#代码,可以看出来事件b的实现。

图7

重点看这里,首先定义了三个action委托实例,this.b是定义的一个委托实例字段,

action=this.b,

actions2 =action,也就是action2=原始委托实例

action3=(Action<string>)Delegate.Combine(action2,value);

这句的意思就是委托的绑定方法的过程内部实现,也就是+=这个内部实现,意思就是将

action2委托实例和新绑定委托实例合并,这里value就是事件绑定的委托

persona.b += new Action<string>(Program.Persona_b)

也就是+=后面的那部分。action3就是绑定之后得到的委托实例。

然后我们分析一下这句话。

action=Interlocked.CompareExchange<Action<string>>(&this.b,action3,action2)

这句话是指,如果this.b和action2相同,那么将action3赋值给this.b同时返回值返回this.b的初始值,如果因为多线程导致this.b和action2已经不相同,那么不更改this.b的值,同时,返回值this.b最新值。然后执行if(action!=action2) gotoLabel_0007;

这里的意思就是就是如果不相同,这里只有一种情况不相同,就是this.b因为并发已经改变成别的值了,所以导致不相同,那么在此循环Label_0007这个代码块,直到this.b是最新的值了。CompareExchange这个方法是通过用户模式锁来处理并发情况的。

从这里可以看出来事件里面其实都是利用委托来实现的。

事件为什么要作为一个成员进行定义,而不支持成为变量或者作为别的方法的形参呢?就像委托一样支持成为变量或者作为别的方法的形参呢?就是因为"发布/订阅"设计模式里面强调发布操作必须由发布者触发。如果事件可以向委托一样,通过变量或者方法参数一样传递来传递去,然后在别的地方触发,那么还能称得上是"发布/订阅模式"吗?所以事件要放在发布者里面,发布者可以是一个类,那么最好的处理就是事件成为类的一个成员,然后这个事件成员不能在发布类外触发,也就是上面的例子不能在外部直接调用persona.b(),只能通过内部方法触发,比如上面的例子那样触发。

public void DoJob()

{

b(Job);

}

也就是b(job)这个时间触发必须在发布类内部触发。这样才能符合"发布/订阅"模式。

今日头条文章有字数限制,请接着看

C#核心-委托和匿名函数揭秘2

标签: #net reflector中文