网站首页 > 技术文章 正文
我们接着《C#核心-委托和匿名函数揭秘1》继续讲解。
上篇我们提到C#事件内部其实是通过委托进行实现的。而且也讲解了事件是实现"发布/订阅"模式的实现之一,而且也讲了委托可以作为方法参数或者变量进行传递的,这个和事件完全不一样。正是因为事件内部是委托这个技术实现的,所以大家一提到事件就会和委托一起讨论。但是我在这里问一个问题?事件只能通过委托这个技术手段实现吗?或者说实现"发布/订阅"模式只能通过委托这个技术实现? 请看下图。
请看图1,图2,图3,我通过接口来实现"发布/订阅"模式。具体代码我就不说了。这里我只是想表达的是委托是实现事件的技术手段,也是实现"发布/订阅"模式的技术手段,我想也可以通过别的方式实现。所以面试的时候,我们最好如上述那样解释,而不是说"事件是特殊的委托",或者说"事件是受限制的委托",这些解释感觉都不太准确。
我并不想多聊事件,因为委托的作用不止如此,因为委托可以作为变量或者方法参数随意传递,所以很多地方委托可以展示他的优势。
C#1.0委托主要用于实现事件。因为实例化委托并不方便,需要创建实例化的过程,需要绑定一个对象的方法或者类的静态方法。那么首先实例化过程麻烦,第二还得创建一个类或者静态方法,才能绑定上去,才能使用,这也挺麻烦的。
接下来,C#2.0引入了匿名函数,C#3.0引入了lambda表达式,从此委托广泛应用起来。我分别对应匿名函数和lambda表达式做个例子,如下图。
如图4,图5,图6,是匿名函数。具体用法大家应该都能看明白。我们先看下图。
图7里面这段代码和图5可以看出来,图5省略
new Action<string>这个实例化过程,直接将匿名函数赋值给了委托实例,还有一点就是我们之前提到的,这里不需要创建一个类和方法或者静态方法绑定到委托实例上了。这样又简便了创建过程。
到了C#3.0,简化到了lambda表达式,直接可以简化到下图
没有关键字delegate,甚至方法都不需要加"()"括号,方法体也不需要加"{}"。当然了,如果参数多个的话还是需要加"()",实现体里面包括多行代码,还是得加"{}"。
上述只是基本用法,我们按照分析事件一样的方式,分析一下编译器对匿名函数和lambda表达式做了什么。我们把代码降级。如下图
从上面代码可以看到降级以后的代码,没有了匿名方法或者lambda表达式代码了,只剩下和C#1.0写的代码一样,创建委托实例需要
new Action<string>(xxx),
然后xxx方法是需要创建一个类,然后创建一个方法,然后把方法指向这个委托类,请看图10里面的"<>c"类以及"<.ctor>b__4_0方法"。
所以这里可以得出结论,匿名方法或者lambda表达式,我这里后面把它们统称为匿名函数,匿名函数本质上还是会创建类和方法去绑定委托。只是C#编译器简化了这个过程,这是C#编译器做的一个魔术而已。触发委托本质上还是触发了类的实例方法。
这个编译器生成的类的实例又可以称为闭包!
想必大家都听说过闭包吧,闭包是不是比较难以理解呢。因为看不到,所以难以理解。
看不到什么呢,就是这个编译器生成的类。如果我们知道这一点,我们是不是相当于看到了,闭包不就是C#给我们生成的这个类的实例吗,因为委托的绑定是需要类的实例方法绑定到,所以需要有一个类的实例
这个实例不只是定义了一个需要绑定的方法,还存了这个匿名方法捕获到的变量,以便后续方法被触发的时候能用到这些变量。什么是捕获到的变量,我举个例子大家便明白,请看下图。
我们把代码降级。
我们这里可以看到C#编译器帮我们生成了"<>c_DisplayClass8_0"这个,这个类里面同时生成了a,a1两个字段。正因为我们匿名方法里面用到了这两个变量,所以我们C#编译器帮我们生成的类就包含这两个字段。你们可以看到,为了参考,我在DelegateText1构造函数里面定义了a,a1,a2变量,DelegateText1类里面定义了A,A1字段,因为我们匿名方法只用了a,a1,所以这两个就叫捕获的变量,捕获的变量就会变成我们生成类的两个字段。
看图12,"class_ = new <>c_DisplayClass8_0()",然后依次给a,a1两个字段赋值。然后把实例方法"<.ctor>b_0"绑定到委托实例action上。这里,这个"class_"就是闭包。
至此我们知道了,匿名方法只是C#编译器变的一个魔术,他变出来了一个类,类里面除了需要绑定委托的的方法,还有匿名方法捕获到的变量也都变成这个类的字段。
所有巧妙使用委托和匿名函数的应用,都是利用了C#编译器的这个魔术,它帮助了我们少写了这个类以及类的成员。表面上我们只写了下面的代码。
Action<string> printReverse = s =>
{
Console.WriteLine(s);
Console.WriteLine(a);
Console.WriteLine(a1);
};
实际上,背后生成了多少代码。这不应该疯狂利用吗?接下来,我举几个实战中的例子。
在winform里面,比如做一个报表,点击按钮,查询数据库,然后展示到界面上。为了不阻塞UI界面,我们会调用一个线程,线程里面查询数据,然后绑定数据源,绑定的过程我们不能在线程里面直接调用UI控件,因为直接绑定会报错。
如下图。
图15,BeginInvoke方法参数本身就是一个委托实例,委托实例绑定了lambda表达式,这里其实就是简化了生成一个类以及方法。图15,其实内部就是类似图14的样子,通过拿到上下文,通过上下文Context.Send(new SendOrPostCallback(xx)) 这里可以保证里面调用的方法是在ui线程里面操作的,不会报错。这个我在《C#核心- async await 揭秘》这一篇文章里面有提过,有兴趣的朋友可以去看下。这个例子只是说简化了生成一个类和方法。
我在另外一篇文章《C#核心-迭代器揭秘》里面谈到了链式编程,推荐大家看看。
在这篇文章中,为了实现链式编程,定义了一个扩展方法,请看下图。
最终实现以下链式编程。
大家试想以下,图17,2,3,4这三部,如果我不用lambda表达式,我是不是得每一个步骤先创建一个类,然后写一个方法,然后将实例方法绑定到参数上。类似这样
var enumerable = values.InterationSample4()
.Select(new Func<string,Class1>(创建一个类.new实例.方法))
.Select(new Func<Class1,Class1>(再创建一个类.new实例.方法))
.Select(new Func<Class1,Class2>(再创建一个类.new实例.方法))
看看一句代码,我的先创建3个类,然后实例化对象,然后把方法绑定上去。如果这么麻烦,我干嘛还用链式编程呢。
上面的例子貌似都没有捕获变量的情况,有没有捕获变量的例子呢?答案是肯定有。
请看下图。
这个代码是DI内部实现简化之后的代码,这里不想很仔细的在讲解下去。因为DI有很多内容。我简单讲一下,DI是依赖注入,目的是实例化对象的过程让系统去做,我们应用的时候只需要通过接口就行了,如下图
这个是某个功能的服务层,这里需要使用其他的服务,但是这里我们看不到其他那个服务的具体实现,我们只看到了接口,这就是我们想要的,面向接口编程。面向接口编程不用实例化对象了吗,并不是,而是实例化这个过程让系统层面去做了。具体面向接口的作用我们需要另外开一篇讲解。这里回过头看图19,这个是注册类,也就是实现DI,首先系统DI得知道一个接口要用什么类实现,那样系统才能实例化对象,然后分配给这个接口,用户才能用。这个ServiceRegister就是收集这个接口以及怎么实例化的对象的过程。这里有很多种实例化的方式,可以直接,如下图。
可以通过接口,和实现类,可以通过自定义的实现方式,可以直接返回一个固定对象。虽然有这么多实现方式,但是都可以通过一个委托来统一,只是不同的接口对应委托绑定的方法不一样而已。
public static Cat Register(this Cat cat, Type from, Type to, LifeTime lifeTime)
{
cat.AddServiceRegister(new ServiceRegister(from, (_, arguments) => _.Create(to, arguments), lifeTime) { });
return cat;
}
比如这段代码,to就是被捕捉的变量。(_,arguments)=>_.Create(to,argument)
这个lambda表达式是实例化过程,这里如果要用C#1.0的实现方式,需要创建一个类,类里面有一个方法,方法里面需要调用Create方法,然后类需要创建一个to的字段,最终需要
new Func<Cat, Type[], object>(类实例.方法)。这样代码就多了,多几个这样的方法就需要多几个这样的类。
这里就是通过捕获变量来简化代码。这里可能你会看不懂,但是不要紧,我们知道原理就行,具体这个例子我还会开一篇具体讨论一下这个优点。
猜你喜欢
- 2024-10-25 优秀后端都应该具备哪些开发好习惯
- 2024-10-25 分享50个让你代码更好的小建议(好用的代码)
- 2024-10-25 Spring AOP里的静态代理和动态代理,你真的了解吗?
- 2024-10-25 代码保护软件 VMProtect 用户手册之准备项目: 使用标记
- 2024-10-25 写代码有这些想法,同事才不会认为你是复制粘贴程序员
- 2024-10-25 用Java创建对象的5种不同方法(java创建对象的几种方式)
- 2024-10-25 DispatcherObject(dispatchertimer)
- 2024-10-25 WPF效果第二百一十篇之NPOI插入图片
- 2024-10-25 【译】ConfigureAwait FAQ(configgenerator翻译)
- 2024-10-25 C# 实现 Linux 视频会议(源码,支持信创环境,银河麒麟,统信UOS)
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)