C#核心-反射揭秘2「建议收藏」

C#核心-反射揭秘2「建议收藏」正如上篇C#核心-反射揭秘1所讲述的那样,C#反射是写框架代码非常重要的一个技术点,要成为优秀的开发者,必须掌握它。接下来我会讲解一下反射的全部

欢迎大家来到IT世界,在知识的湖畔探索吧!

正如上篇C#核心-反射揭秘1所讲述的那样,C#反射是写框架代码非常重要的一个技术点,要成为优秀的开发者,必须掌握它。接下来我会讲解一下反射的全部技术点,然后一一举例具体的应用。

上篇说了依赖注入DI的用法,最终是通过以下方式使用,请看图。

C#核心-反射揭秘2「建议收藏」

图1

就是通过接口的方式使用服务,上篇也大概说了一下原理,没有看的朋友请先看一下。

我还是大致说一下过程,图1其实是我写的api接口,api接口调用会有一个路由,框架会解析路由然后通过反射实例化图1这个GoodsController对象,反射实例化GoodsController首先需要实例化构造函数里面的形参,这些形参都是接口形式,但是实际上会实例化他们的派生类,然后派生类实例化对象赋值给这些接口,这里用到的是里氏替换原则,派生类放在别的地方我这里就不展示了。实例化派生类的过程中,也会遇到派生类里面构造函数里面也会有一些形参,那么也得实例化那些形参才行。所以这里会有一个递归实例化的过程,最终实例化最源头的GoodsController对象,这个过程说的比较抽象,大家知道大概概念就行,后面会通过代码详细讲解。

这样的做法有什么好处呢,好处就是从依赖派生类转为依赖接口,这样就减少了依赖,因为接口只是定义一些方法名,具体实现在派生类,如果派生类名称需要修改,或者换一个派生类的话,通过接口的方式不需要改动,这里就说到了一个叫开闭原则,开闭原则就是对修改封闭,对拓展开放,也就是写过的代码少改,通过拓展的方式添加代码,这里接口定义的话就是少改了代码了。

这里关键是需要知道接口和派生类的关系,不然DI框架也不知道怎么实例化对象赋值给接口了。接口和派生类的实现我们可能放在很多dll里面,所以这里就要提到加载程序集的功能。我们都知道类型和接口是放在程序集中的,所以DI框架会收集接口和对应派生类的关系放在一个集合中以便后续使用,所以我们先说怎么加载程序集。请看下图

C#核心-反射揭秘2「建议收藏」

图2

请看图2,我们加载一个dll的方式是这样的,当然这个dll需要在我们的运行目录里面。

var assembly = Assembly.Load(new AssemblyName(){Name=”xxx”});

通过assembly就可以就可以解析出来里面的接口以及派生类关系了,这个我们后面再说。

如果你的接口和派生类都在一个dll里面,你就可以这么干,当然还可以这么使用,请看下图

C#核心-反射揭秘2「建议收藏」

图3

图3,可以通过程序集的字符串进行查找。你还可以通过路径查找。

C#核心-反射揭秘2「建议收藏」

图4

如图4这样 Assembly.LoadFile(FilePath)。还可以通过下图

C#核心-反射揭秘2「建议收藏」

图5

Assembly.LoadFrom(dll文件名)。

还有获取当前使用的程序集合,如下面这句,意思就是拿到当前程序集。

Assembly.GetExecutingAssembly();

以上都是加载一个程序集。

当然很多情况下我们的接口和派生类是放在不同的程序集里面的,比如你有一个service层,也有一个repository层,一个放逻辑,一个放仓储,不通的程序集。

这个时候我们可以可以这么做,请看下图。

C#核心-反射揭秘2「建议收藏」

图6

首先通过Assembly.GetEntryAssembly().Location)找到当前应用程序集exe所在的路径

C#核心-反射揭秘2「建议收藏」

图7

请看图7,这里是具体文件

然后找到这个文件所在目录,也就是运行目录,接口找到这个运行目录下的所有的dll程序集地址。var addInAssemblies = Directory.EnumerateFiles(addInDir, “*.dll”);这个返回的是一个序列。这样我们可以通过linq循环拿到每一个的程序集。

还可以通过下图。

C#核心-反射揭秘2「建议收藏」

图8

图8这样拿到程序集AppDomain.CurrentDomain.GetAssemblies()。这里是通过应用程序域,应用程序域是进程下clr的功能,它支持多个应用程序域,彼此隔离内存无法直接相互引用,这相当于支持动态加载dll,一个进程可以支持多个应用程序域然后彼此隔离。每个进程首先会分配一个默认的应用程序域,这个应用程序域我们后面另外开篇再讲。这个拿到的程序集合就会包含了一些系统程序集,虽然后面我们在获取接口和类的时候会做过滤,但是这样拿到的程序集合比较多,效率会差一点。

还可以通过下图。

C#核心-反射揭秘2「建议收藏」

图9

先获取当前应用程序域的地址,也就是运行目录,然后通过

Directory.GetFiles(baseDirectory, “*.dll”)

模糊查到我们想要的dll的路径地址。这样后面我们可以通过Assembly.LoadFrom拿到程序集了。

我们这里的AppDomain.CurrentDomain,可以使用Thread.GetDomain()替换,他们都指向同一个应用程序域。

现在我们得到了Assembly或者Assembly集合,然后我们就可以通过反射得到接口和派生类的集合,请看下图。

C#核心-反射揭秘2「建议收藏」

图10

图10代码的目的就是收集接口和派生类的关系,我们通过

Dictionary<Type,Type> 结构来收集接口和派生类的关系,当然实际DI框架里面没有这么简单的设计,这里只是说清楚概念而已。

这里通过

Assembly.GetExportedTypes().Where(t=>t.IsInterface == true)

拿到所有接口定义

assembly.GetExportedTypes().Where(t => interfaceInfo.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract).FirstOrDefault();

通过这句代码拿到接口对应的派生类

GetExportedTypes方法是指获取公共类型,这个包括类,接口,枚举。上面代码获取接口的时候用t.IsInterface == true进行过滤拿到接口序列,后面这句代码通过

interfaceInfo.IsAssignableFrom(t) 这句话的意思是interfaceInfo是否可以被t替换,意思就是里氏替换原则,派生类可以赋值基类或者接口,返回bool,进行过滤,意思就是拿到接口的实现派生类,并且不能是接口和抽象类。拿到的结果可能会有多个,我们这里取第一个。

然后我们就可以收集接口和派生类的关系了。接下来就是使用的时候从这个集合拿到类,然后通过

Activator.CreateInstance(Type)

方式进行实例化对象。

上面提的例子,就是api接口的方式,就是这样创建控制层实例,然后调用方法,调用过程也是反射方式,我们接着讲。

上面讲述了获取程序集和类型的技术,接下来讲一下实例化对象。实例化对象包括实例化无参构造函数类对象,实例化有参构造函数类对象,实例化数组对象,实例化泛型类对象。这里每一个反射实例化对象都很重要。

C#核心-反射揭秘2「建议收藏」

图11

C#核心-反射揭秘2「建议收藏」

图12

请看图11和图12,这里定义了4个类,Person,Body,Head,Foot,其中Person类的构造函数里

有Body类型形参,Body类构造函数里面有Head和Foot类型形参。我们的目的是通过反射实例化Person类。

C#核心-反射揭秘2「建议收藏」

图13

图13是反射实例化方式之一

再看图11,首先Person类只定义了一个有两个参数的构造函数,没有无参构造函数,所以必须先实例化构造函数里面的参数,然后才能实例化Person类。

如果直接调用Activator.CreateInstance(Type type)不传参数的话,实例化Person会直接报错。所以先得找到构造函数,然后实例化每个构造函数对象,然后拼成数组,再调用

Activator.CreateInstance(Type type ,object?[]? args)。

type.GetConstructors().FirstOrDefault();

这句话就是找到第一个构造函数,因为是反射创建实例化对象,到底用哪个构造函数,是需要有一个策略的,但是最终都需要决定其中一个构造函数,我们这里就默认第一个就行了。

ParameterInfo[] ps = ci.GetParameters();

这句话是拿到构造函数里面的参数,通过

ParameterInfo.ParameterType可以知道参数的类型Type

然后我们无法得知Type是否有有参构造函数,所以这里我们需要用一个递归拿到最终的实例

constructorParams.Add(Create(pi.ParameterType));

constructorParams是实例化对象数组。

C#核心-反射揭秘2「建议收藏」

图14

我们执行一下图14,得到结果。

C#核心-反射揭秘2「建议收藏」

图15

可以看到我们通过反射获取到了Person对象。

Activator.CreateInstance是一种反射创建实例化方式之一,当然也有很多重载方法,还有一个就是

C#核心-反射揭秘2「建议收藏」

图16

这个是通过构造函数对象执行实例化对象。效果是一样的。

上述代码几个类的耦合太厉害,接下来我们结合DI的思路来创建Person引入接口

C#核心-反射揭秘2「建议收藏」

图17

C#核心-反射揭秘2「建议收藏」

图18

请看图18,新增了IBody,IHead,IFoot三个接口,其中请看Body里面构造函数里面,已经变量IHead,IFoot接口类型参数了,再看图17 Person类里面的构造函数参数也变成IBody接口类型参数了。看我们是怎么实现的。

我们定义了一个

static Dictionary<Type, Type> registerDic = new Dictionary<Type, Type>();

全局静态字典,存储接口和实现类的关系。

看Main方法里面,首先通过反射加载程序集,拿到接口和派生类的关系存入刚才的registerDic里面。这块我们开篇已经讲述过了。

接口看Create方法里面

registerDic.TryGetValue(pi.ParameterType, out var type1);

constructorParams.Add(Create(type1));

这两条代码,第一个是通过类型从字典里面找到派生类,然后实例化派生类。

C#核心-反射揭秘2「建议收藏」

图19

我们看图19 结果,我们已经实例化了Person对象,而且可以通过接口执行方法。

这里最简单的实现了一下DI的原理,当然这里我想表达的是反射在其中的作用。

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

C#核心-反射揭秘3

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/22840.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信