我们之前C#核心-反射揭秘1 C#核心-反射揭秘2 C#核心-反射揭秘3的这三篇文章里面讲解了一下反射在DI中的应用,讲到了如何通过反射加载程序集,实例化对象,实例化对象又包括实例化普通对象,实例化泛型,实例化数组,反射执行方法等。

我们在第三篇里面提及到了几个类型。

ContructorInfo和MethodInfo,一个代表构造函数,一个代表方法,两者都可以获取他们的参数以及执行他们的方式。

其实通过类型Type我们可以获取所有成员,不光是构造函数和方法,还可以拿到字段,属性,事件,内部类,他们分别对应FieldInfo,PropertyInfo,EventInfo,TypeInfo

他们的继承结构是这样的。

c语言星阵解析(核心-反射揭秘4)(1)

图1

反正只要你平时针对这些成员写的代码,通过反射都可以实现。我这里就不一一举例了。

我们回到第一篇里面讲的另外一个例子,我们详细地讲解一下代码。

c语言星阵解析(核心-反射揭秘4)(2)

图1

c语言星阵解析(核心-反射揭秘4)(3)

图2

c语言星阵解析(核心-反射揭秘4)(4)

图3

c语言星阵解析(核心-反射揭秘4)(5)

图4

第一篇我们说到,api接口定义的枚举,比如图4,这是一个运货单的状态枚举,当然在我们后台接口api里面会经常用到这样的枚举,比如运货状态的判断以及修改都使用一下这样枚举,总比直接在代码里面写状态码要来的方便。但是这样的枚举其实前端也用得着,因为如果前端需要做一个报表,需要用到这个运货单状态进行查询运货单数据的话,前端也需要写这么一个查询的字段,一般处理就是前端写死一个状态集合,但是这样的话,如果变更了状态,后端改了,没有及时通知前端,或者前后端更新的时间不能再一起,那么就会出现问题。最好的方式就是通过接口把这个运货单状态返回前端,提供前端使用,那么后端修改之后,前端不需要再修改。

前端是需要状态值和状态显示的,比如,状态值10对应"待接单",那么我们可以定义一个特性Description,然后把描述值写上,然后我们通过反射的方式拿到这个对应关系返回到前端。

请看图2。

BuildSelectItemsForEnum<T>

我们定义了一个泛型方法,泛型具体类型是一个枚举。

if (!typeof(T).GetTypeInfo().IsEnum)

这句话的意思是必须是枚举,不是枚举直接返回null。

var values = (Enum.GetValues(typeof(T)) as T[]);

这句话的意思是拿到枚举值,也就是,10,20这种值。

Convert.ToInt32(w).GetDescription<T>()

这个方法是通过值拿到对应的描述值,也就是"待接单","已接单"。

看这个方法是怎么实现的。看图3

if (!typeof(T).GetTypeInfo().IsEnum)

首先需要判断泛型是枚举类型,不然直接返回val值,也就是直接返回10,20,而非描述值。

if (!Enum.TryParse(val.ToString(), out T enumValue))

这句话是判断value值在不在枚举里面,如果在,则继续执行,然后输出枚举值enumValue,不在,说明数值不对,直接返回val。

var fieldInfo = typeof(T).GetTypeInfo().DeclaredFields.FirstOrDefault(w => String.Compare(w.Name, enumValue.ToString(), true) == 0);

这句话是关键,通过T拿到类型,然后拿到字段集合,过滤名称是上面拿到enumValue的名称,是的话就拿到了枚举成员fieldInfo,这个字段类型是FieldInfo,上面我们讲到这个类型是字段,也就是在反射里面,枚举成员是字段。

var attr = fieldInfo.GetCustomAttribute<DescriptionAttribute>();

然后我们通过拿到特性,特性是DescriptionAttribute,然后

return attr.Description;拿到特性值,也就拿到了描述值了。

然后我们把值和描述组成一个数组返回到前端。

这个反射的例子也非常的实用,想一想,如果不用反射我们会怎么做。

我们是不是得写死一个集合然后返回到前端?毕竟这个状态没有维护到数据库,我们无法从数据库中查找,只是维护在枚举中。写死意味着如果修改或者新增一个枚举值,枚举要改,这个写死的地方也得改。这样我们就知道这个反射的好处了。

我们再举一个例子,然后结束反射这个话题。

winform里面的mvp模式里面的反射应用。

c语言星阵解析(核心-反射揭秘4)(6)

图5

c语言星阵解析(核心-反射揭秘4)(7)

图6

c语言星阵解析(核心-反射揭秘4)(8)

图7

c语言星阵解析(核心-反射揭秘4)(9)

图8

c语言星阵解析(核心-反射揭秘4)(10)

图9

c语言星阵解析(核心-反射揭秘4)(11)

图10

c语言星阵解析(核心-反射揭秘4)(12)

图11

请看我创建了一个winform程序,FrmMain是主界面,类FrmMain加了一个特性

[PresenterBinding(typeof(MainPresenter))]

这个特性代表的是当前主界面绑定的主导器是MainPresenter。

这里你可能会问,什么是主导器,什么是MVP模式?

MVP的全称为Model-View-Presenter,Model提供数据,View负责显示,Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter(MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过Controller。

上面是摘自百度百科的定义,可以看出来MVP抽象出来一个Presenter层(后面统称主导器),处理业务逻辑,处理数据,所谓数据,可能是来自数据库,可能是来自别的地方。view层也就是winform界面了。意思就是主导器做所有的逻辑处理,包括数据处理,比如查询数据库,而view只是用于显示,而主导器和view通过接口进行交互。举一个简单的例子,做一个报表,有一个查询按钮,有一个展示的列表,点击按钮,触发前端按钮事件方法,然后直接触发接口对应的事件,而这个事件绑定的方法已经在主导器进行了绑定,也就是前端触发按钮事件,最终调用了主导器定义的方法里面去了,然后主导器到数据库查询数据,然后通过接口触发前端的方法,把数据展示出来。这里前端只需要定义展示的方法,这样逻辑层和视图层就完全隔离了。

这样说还是抽象,我们还是继续讲解代码。请看下图

c语言星阵解析(核心-反射揭秘4)(13)

图12

图12 界面只有一个按钮和一个label进行展示,我这里就是要展示点击按钮,触发主导器,然后主导器处理数据,然后通过接口返回到页面进行展示。

c语言星阵解析(核心-反射揭秘4)(14)

图13

图3 主界面绑定了一个特性,MainPresenter,然后我们看主界面FrmMain继承了FrmBase和IMainView。IMainView就是主导器和前端界面之间的交互的接口,我们来看是怎么把主导器和前端界面联系起来的。我们看FrmBase类。

c语言星阵解析(核心-反射揭秘4)(15)

图14

请看图14

private readonly PresenterBinder _controller = new PresenterBinder();

这个是定义了一个主导器绑定类

然后在FrmBase的构造函数里面

_controller.ControllerBinding(this);

这样就绑定上去了,this代表的是当前界面。我们来看下这个方法。请看下图

c语言星阵解析(核心-反射揭秘4)(16)

图15

public IPresenter ControllerBinding(IView view)

方法定义是这样的,因为FrmBase继承了

Form, IView,所以ControllerBinding的参数可以是IView。

object[] attrs = t.GetCustomAttributes(typeof(PresenterBindingAttribute), false);

这句话我们就熟悉了,是通过反射的方式获取一个类型的特性,特性的类型是PresenterBindingAttribute,t代表的是当前页面,也就是FrmMain,而FrmMain绑定的是

[PresenterBinding(typeof(MainPresenter))]

也就是attrs其实就是这个MainPresenter

presenter = (IPresenter)Activator.CreateInstance(((PresenterBindingAttribute)attrs[0]).PresenterType, view);

这句话就是实例化的过程了,实例化MainPresenter

而MainPresenter构造函数参数是

public MainPresenter(IMainView view)

这样就创建了主导器了,且这个主导器就可以使用传入进来的这个view,这个view就是

FrmMain界面。为什么MainPresenter构造函数的参数是IMainView view,而实例化的时候似乎是IView。

c语言星阵解析(核心-反射揭秘4)(17)

图16

虽然图16可以看出来IMainView继承的是IView,但是也不能IView赋值给IMainView接口呀?这里我们要知道这里的IView的对象是FrmBase执行的那句

_controller.ControllerBinding(this);里面的这个this,这个this其实是派生类FrmMain,所以表面上看到的是IView,但是实际上是FrmMain,所以可以赋值给IMainView,因为FrmMain继承了IMainView。

这里我们可以看到MainPresenter实例化之后的对象没有赋值给任何地方,那么你会不会又一个疑问,没有赋值给任何地方,它不会被GC回收吗?不会,因为主导器绑定了view对象,也就是构造函数里面的这个IMainView对象,它不被回收,主导器也肯定不会被回收。

我们看MainPresenter主导器类

private readonly IMainView view;

我用一个字段记录了一下这个视图,然后构造函数里面

this.view = view;

这里面熟不,为什么直接是接口赋值就可以使用这个服务了,因为实例化的时候已经给构造函数的这个接口赋值了对象了,也就是主界面对象。

this.view.BtnClick = View_BtnClick;

这里可以绑定按钮事件对应的方法。

c语言星阵解析(核心-反射揭秘4)(18)

图12

我们主界面实现了这个接口,并触发这个事件,这样就可以触发主导器的View_BtnClick方法

private void View_BtnClick(object sender, EventArgs e)

{

this.view.ShowData("主导器发出命令,请前端展示");

}

这个方法只调用了这个接口的ShowData方法,但是我们可以发挥想象,这个方法可以是查询数据库,也就是model层。我这里是做了一个简化,直接返回"主导器发出命令,请前端展示"。

c语言星阵解析(核心-反射揭秘4)(19)

图13

FrmMain实现了这个接口方法,然后展示到前端。

我们梳理一下这个过程,view层的FrmMain和主导器MainPresenter是通过一个特性进行了绑定,实例化过程是在基类FrmBase进行,主导器MainPresenter和视图通FrmMain过IMainView进行交互。我们这里可以看到IMainView只是传递事件然后显示数据,没有其他动作了,关键动作在MainPresenter里面,而实例化这个主导器是通过反射进行的,一个很简单的

Activator.CreateInstance()方法。

再说说MVP的好处是什么,因为我们完全可以不用这么做,完全可以使用我们熟悉的三层架构,直接前端调用逻辑层,然后拿到数据,然后展示不就完了,还需要这么麻烦要通过一个主导器然后通过接口传来传去吗?

是的,如果一个简单的应用完全用不上mvp,mvp的好处就是主导器可以复用以及方便前端测试。可以想想如果一个产品同时需要用于winform和wpf,开发一样的功能,这个主导器其实可以公用的,要改的只是前端代码,因为主导器和视图是通过接口交互的,接口交互意味着耦合是比较小的,因为抽象,定义的东西两边都公用。而且主导器和视图可以分开不同人开发,两个人只要定义好公共的接口就行。甚至主导器是外部的一个dll都行。所以mvp这种模式适合比较大的公司和项目,不像我们这些挫逼所有事情一个人都干完了。

,