我们在文章C#核心-反射揭秘2里面讲解了通过反射获取程序集,通过程序集获取接口和类,然后通过反射实例化了对象,然后通过一个简单的依赖注入DI来讲解一下构造函数注入方式,这个方式的特点是构造函数里面的形参都是接口,这样减少了耦合,让实例化过程交给框架去做。
上篇讲到的是注入的都是普通接口,但是其实还可以注入普通对象,泛型接口以及接口数组。
也许你会发问,不正是因为注入类导致强耦合,才使用注入接口的方式吗?
是的,但是特殊情况特殊分析,如果外部框架提供给我们的api是类而非接口的话,我们肯定是也只能是使用类注入,当然我们也可以直接实例化,不用注入。
比如efcore,请看下图。
请看图1,这个类就是efcore使用的类,它只提供给我们类而非接口,只要实例化这个JhwContext,就可以使用里面我们定义的实体的DbSet字段进行增删改查,所以我们注入的时候就是这么注入的,请看下图。
看上去并没有实例化过程,但是注入了类,但是它和接口注入是类似的。请看下图
图3所示,
services.AddEntityFrameworkMySql().AddDbContext<JhwContext>
这句话是注入这个类型对应的"实例化过程"到对应的字典里面去。这里处理的方式和上篇提到的初始化过程接口会绑定派生类形成一个关系不一样,这里创建实例是一个过程,所谓过程就是方法,所以类型Type对应的是一个委托,因为委托可以绑定方法,且定义了方法的规范,也就是入参反参的规范,而委托绑定的方法才是真正实例化的过程,所以这个比接口以及实现类对应关系会复杂一点,因为本篇主要讲反射而非DI,所以我们只需要知道每一个类型Type会对应一个实例化它的一个委托就行了,而注入类型的过程其实并没有用到反射。
而事实上DI类型绑定的就类似委托,而非我们上篇写的Dictionary<Type,Type>这么简单,使用委托更加灵活,拓展性更强。不同的Type,我可以用同一种类型的委托但不同处理方法绑定的委托进行关联,最终实例化的过程我只需要执行委托就能拿到我想要的实例化对象,而这个执行委托的过程是统一的,因为是同一个类型的委托,定义了相同的入参和反参。
我在C#核心-委托和匿名函数揭秘1这篇文章也提过一句这样做的好处,简单说就是简化了代码,每一个不同方法对应的委托其实都是编译器自动为我们创建了不同的类和方法然后实例化一个对象然后把实例方法绑定到了这个委托上,而我们只需要写一个匿名方法就可以做到这个过程,大大简化了代码。这是DI的内容,我们会再开一篇单独详细的讲解,这里不多加叙述了。
如果注入的是一个泛型数组,比如IList<T>会怎么样。请看下图。
请看图4,Person类构造函数多加了一个形参,IList<IBody> bodyList
这里注入的方式是接口方式,并没有实例化过程,如果要手写实例化应该是
IList<IBody> bodyList = new List<Body>();
但是这里没有这个实例化过程,实例化过程放在了框架里面,也就是Create方法里面,请看图5,首先还是在构造函数,找到参数。
pi.ParamterType.IsGenericType
这句话是判断是否是泛型
pi.ParamterType.GetGenericTypeDefinition()
这句话是获取泛型类型的泛型开放类型。
这里多说一句泛型有开放和封闭类型,IList<> 这样就表示泛型开放类型,因为还没有指定泛型的具体类型是什么,IList<IBody>就是泛型封闭类型,泛型封闭类型就是已经指定泛型的具体类型了。我们这里首先需要确定是IList<>这样的开放类型,然后再继续执行
if (pi.ParameterType.GetGenericTypeDefinition() == typeof(IList<>))
这里必须这么判断,因为可能还存在别的泛型类型,比如IEnumerable<>,处理的方式不一样的。
pi.ParamerType.GetGenericArgument()[0]是拿到泛型封闭类型的第一个类型,因为IList<T>就一个类型,所以取第一个,然后从字典里面拿到T对应的派生类,然后执行
Array.CreateInstance(Type1,0)
这句话的意思是实例化一个Type1类型的数组,因为数组是空的,所以指定是0。
大家可以看到这个过程都是反射做的,你可不能使用
Activator.CreateInstance(Type)来实例化一个数组,会报错的。
如果你有疑问,注入泛型数组有什么用,我只能诚实地和你说,我也不知道什么用,我总感觉很重要,但是我还没有遇到这样的实际例子,如果你知道,请写下评论。
如果注入的是一个泛型类型怎么办。如下图。
图6 Person类构造函数添加了一个新的参数IBulk<IFoot>,图7是接口的定义和派生类的定义,看图9,因为之前做的通过程序集拿到接口和派生类的关系的时候,是不考虑泛型的,所以我们手动添加了这个泛型接口和派生类的关系,请看图9
registerDic.Add(typeof(IBulk<>), typeof(Bulk<>));
这个就是定义了泛型开放类型接口和泛型开放类型派生类的关系,这里不用封闭类型,是因为泛型么肯定支持多类型,如果搞一个封闭类型没有任何意义,意思注册的过程用开放类型,实际注入使用的时候用封闭类型,想一下就明白了,当然要这么做,难道注册的时候用封闭类型?那有100个封闭类型注册时候我需要写100次?所以注册的时候用泛型开放类型,只需要写一次。
看图8,来讲解一下我们是怎么实现泛型类型注入的。
首先还是会判断构造函数参数是否是泛型,如果是泛型,看泛型开放类型是否是IList<>,
不是的话,也就是普通的泛型。
var parameterTypeDefinition = pi.ParameterType.GetGenericTypeDefinition();
这句话是拿到类型的泛型开放类型的Type。
registerDic.TryGetValue(parameterTypeDefinition, out var type1);
然后从字典里面拿到泛型开放类型接口所对应的泛型开放类型派生类。
var fromType = type1.MakeGenericType(pi.ParameterType.GetGenericArguments());
然后通过这个方法把泛型开放类型派生类变成泛型封闭类型,因为泛型接口的泛型参数列表和派生类的泛型参数列表是一样的,所以可以这么干,其中
GetGenericArguments方法是拿到泛型参数。
MakeGenericType方法就是从泛型开放类型转到泛型封闭类型,为什么最终要拿到泛型封闭类型呢?因为只有泛型封闭类型才能实例化对象,泛型开放类型不行,这个我们想一下就知道了,当然得知道最终泛型是什么类型才能实例化对象,不然实例化个毛,细想一下就明白了。
constructorParams.Add(Create(fromType));
这句意思就是实例化泛型封闭类型,然后把对象赋值给构造函数参数以便后面Person得实例化,这里有一个递归的过程,因为泛型类型里面构造函数也可能存在泛型参数等等,这个递归过程第二篇里面已经讲过,这里不再多加描述了。
到这里我们通过一个简易的DI框架这个例子讲解了反射的用法,包括程序集加载,实例化过程,实例化过程又包含普通类型,泛型类型,数组类型。
接下来我们继续讲解通过反射执行方法。
var person = (Person)Create(typeof(Person));
person.ExecWalk();
我们之前是通过上面两行代码实例化Person类,然后执行方法,我们现在改造一下。
请看下图。
请看图11,我们不直接拿到Person对象,然后执行方法,而是通过字符串的方式反射执行找到Person类,然后反射执行ExecWalk方法。
请看图10这个Person类,我们继承一个Controller基类,用户通过反射快速获取Person类。
请看图12
Type controller = assembly.GetExportedTypes().Where(t => t != typeof(Controller) && t.IsAssignableTo(typeof(Controller)) && t.Name == className).FirstOrDefault();
这句话的意思是从当前程序集获取内部公开类型,类型不是Controller类型且是从Controller派生的,然后类型的名称是className,这里就是我们传入的字符串"Person"
var obj = Create(controller);
这句话就是实例化这个类,也是通过反射方式,和上面讲解的一样
MethodInfo methodInfo = controller.GetMethod(funcName);
这句话是通过字符串funcName,然后Type controller的GetMethod方法获取对应方法
也就是MethodInfo,然后调用
methodInfo.Invoke(obj, objs);执行方法
第一个参数是实例化对象,第二个参数是方法参数。
这样我们就通过反射执行了方法,得到结果。
我们在第二篇说过,接口api会接受调用的路由,然后解析出来需要执行哪个类和哪个方法,以及收集到http请求对应的调用参数。这里看看我们上面模拟的是不是很类似,只是我们没有模拟从路由地址如"http://ip:端口/Api/Person/ExecWalk"解析类型和方法,我们直接是模拟拿到了最终的类型和方法字符串,然后通过反射去执行方法。
现在我们初步知道了通过反射执行方法
今日头条文章有字数限制,请接着看
本文暂时没有评论,来添加一个吧(●'◡'●)