Asp.net 面向接口可扩展框架之类型转化基础服务

新框架正在逐步完善,可喜可贺的是基础服务部分初具模样了,给大家分享一下

由于基础服务涉及面太广,也没开发完,这篇只介绍其中的类型转化部分,命名为类型转化基础服务,其实就是基础服务模块的类型转化子模块

说到类型转化必须要弄清楚.net的类型,类型都不清楚何来类型转化

1、Primitive类型

     1.1 这个概念估计很多人都没听说过,Primitive不是一个新类型,而是.net类型中最基本的一种分类,是基元类型的意思

        MS将类型分为三类:Primitive(基元类型)、Complex(复合类型) 和 Collection(集合类型),Type 提供了IsPrimitive 属性

     1.2 哪些类型属于Primitive呢?

          参考链接:http://msdn.microsoft.com/en-us/library/system.type.isprimitive(v=vs.110).aspx

          ”The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.“

     1.3 看到这里还是挺失望的,我们常用的类型Decimal和String并不含在内

         有的同学可能说,别说那两个了,里面就除了Char和Double其他都没用过;老实说MS真的把.net程序员彻底惯坏了

         其实Boolean就是我们常用的bool,Int32就是int,Int64就是long,Single就是float

   

     1.4 Primitive类型有什么用呢?

         Primitive是基元类型,可以这样理解,其他类型都是由Primitive类型构成的或者衍生的,.net对Primitive支持最好,"有些地方"对于非Primitive类型支持不好,可能造成"灵异"事件(对于一般的项目是很难碰到的,如果你能碰到,说明你对.net钻研的比较深了,普通的工程师就更不要嚷嚷说.net类型有缺陷不敢用之说,最多底层框架开发者或者架构师小心一点)

   这里就不展开了

2、IConvertible类型

  IConvertible并不是新维度的类型类别,IConvertible是MS定义的一个重要接口,我这里讲的IConvertible类型特指Ms定义的基本类型中继承了该接口的类型

  我来稍作解析一下,继承了IConvertible的类型非常强大,可以方便的转化为常用的这15种类型,也正是这15种类型继承了IConvertible接口

  也就是这15种类型可以方便的相互转化(其实不一定的,有些转化是会出错的)

     IConvertible类型转化具有高度的扩展性,可以传一个格式化的参数(IFormatProvider)

     注:在我看来IFormatProvider主要是作用是字符串(string)类型和其他14种类型相互转化之用,string被Ms定义成了"通用类型",我认为是数据形态之一(我把数据分成几个形态,强类型,弱类型(object),string,二进制(byte[]),数据流(Stream),这个会在序列化基础服务中再详细讲解)

 

 3、IFormattable类型

     IFormattable和前面IConvertible类似,也是Ms定义的一个重要接口,继承这个接口的类型非常多,这个接口主要是把对象转化为特殊(格式化)的字符串。

     

     基本类型中的byte\DateTime\Decimal\Double\short\int\long\sbyte\float\ushort\uint\ulong等都继承,除此之外还有很多系统类型也都继承IConvertible(这里不再枚举),我们自定义类型也可以方便的实现IConvertible接口

     鉴于字符串(string)的重要性,该接口相关的服务对应用开发非常有用(但是很多人都不清楚)

 

 4、"基本"类型

     这里是我定义的”基本类型“,我认为有这些类型就足够开发使用,框架对此之外的类型不予"支持",或者这些类型优先支持

     包含类型列表:bool、byte、char、decimal、double、float、int、long、string、DateTime、byte[]

     注1:在现在内存这么便宜的时代,我认为短数据类型的作用不大,短数据类型都使用int就好了

     注2:我也不喜欢使用无符号类型,所以又省略了一大批类型,对于数字类型Ms使用==0表示"逻辑空",我使用<=0表示"逻辑空",后面还有相关例子

     注3:这样做也是情非得已,类型太多了,其排列组合更多,好在本框架是可扩展框架,使用本框架的时候可以自己扩展支持的类型,当然也可以开发一个类型扩展的组件(类库),需要的时候自己注册进去


 现在开始演示类型转化基础服务

一、基本类型转化

1、类型太多,先测试字符串转int

2、不同数字类型转化为Int

这里我要多唠叨一句,我前面说过对”基本类型“以外的不予支持,其实是不"支持"转化为这些类型,这些类型转化为”基本类型“是没有问题的

上面例子里面有小数(浮点)转整数(int)的例子,Ms有自己的一套“四舍五入”的规则,如果不满意可以自己扩展注册进去(一般情况下调用的地方并不需要修改)

另外,会尝试IConvertible的类型相互转化,如果Ms支持的很好的IConvertible转化.这里也没道理不支持的

3、object转化为int

以上例子可以看出,基本类型转化完全没有问题,其实在主框架,我也不打算默认实现复杂自定义类型的转化,这个留给扩展实现

有人可能会说你上面的这些功能So easy,实现过类似功能的多如牛毛,不过是多造个轮子而已。确实,基本类型转化实现简单,但是这里要强调,我做的是可扩展框架,可扩展是亮点,而且非常容易扩展

 

二、自定义类型转化为基本类型

1、先看自定义类型代码

非常简单还是解读一下,定义了两个类型,customObj明显是个模型(Model)类,customConverter有一个方法传入一个customObj对象返回一个int值

2、再看对象转化代码

首先我们看结果,是我们预期的效果,和customConverter的Get方法的效果一致,但是我们没有直接调用customConverter.Get,甚至好像和customConverter类型都没什么关系

这里有几个关键因素,其一是GlobalServices.Convert方法是怎么运行的;其二是GlobalServices.CreateContainer()是什么鬼

 

三、源码解析

1、GlobalServices.Convert方法解析

这个类型转化还是挺复杂的,挑主要的来解读

1.1 TryConvert是尝试类型转化,如果转化失败可以换一种方法再做

这种方式在本框架中有大量应用,其实就就形成一个”策略链“,每个”策略“判断一个这个问题自己是否是自己可以处理的类型,不能处理,下一个策略继续,这样非常便于扩展

在TryConvert中先调用个接口IEntityConvert,转化失败再调用IEntityAccess用来转化

这里有一个特别重要的事情,就是这个接口的对象从哪里来,这里是来自一个容器对象,这里很清楚的看到本框架的一个扩展点,只要往容器里面添加"策略"就可以增强本框架的类型转化功能,这是我为什么说主框架不打算实现复杂的类型转化,真的非常容易扩展

1.2 再看Convert主方法

A:先按当前类型尝试转化

     如果策略库(容器注册)里面有当前两种类型转化的策略,性能是最好的,优先执行

B:转化失败判断当前对象是否为空

  对象为null,放弃转化,直接返回默认值

C:Transform.TryConvertByType

  这个简单就是强制类型转化,如果T是S的基类,这个时候就可以直接转化过去,也是非常安全的 

D:再尝试IConvertible转化

    前面说到系统的IConvertible定义了15种基本类型的相互排列组合转化,而且还可以使用IFormatProvider自定义转化,这就是一个强大的转化机器,不能不试

E:再检测返回类型是否为string,如果是string直接调用对象的ToString()

F:尝试通用类型转化(IConvert)

  这个地方可以把第三方的Mapper工具封装为IConvert接口注册进来(后面还有讲解)

G:最后尝试把s对象转化为字符串,然后把字符串转化为目标类型(T)对象

   其实这里把字符串作为基本数据格式,相当于与对s对象序列化为字符串,然后把字符串反序列化为T类型对象,也不怪我怎么用,.net所有类型都有一个ToString()方法,所以string是个不错的中间类型

功能是不是非常强大,也非常有别于很多类型转化工具,上来先反射,获取类型元数据,然后调用属性和字段,现在还没做测试,我这种方式可能会有明显的性能优势,但是性能现在不是我最想考虑的问题,我现在考虑的是怎么可以非常简单的扩展

2、GlobalServices.CreateContainer()是什么鬼

CreateContainer定义

A:从上面可以看出GlobalServices.CreateContainer就是一个再普通不过的容器,默认实例的容器名是GlobalServices

B:前面有提到容器是可以扩展的,只要我们使用的容器支持(比如Unity、Spring.net等),我们完全可以使用配置文件来扩展类型转化,还可以使用非常炫的IOC和AOP等特性

C:说到这里,现在"业界"有几个类型转化工作可以做到?我稍微看了一个现在非常流行的Automapper,自定义映射确实没问题,但是映射过程还可以通过配置文件扩展或者IOC和AOP的我没见识过,如果有人看到过请告知,我要好好学习学习

D:早期看过这篇文章的可能发现,变化很大,我把GlobalServices由静态类修改可以实例化的形式,这样就允许我们创建多套服务配置管理对象(使用子容器技术,继承默认服务并覆盖少数服务),进一步提高可扩展性

3、其实GlobalServices容器和其他容器还是有点区别的,继续深挖源码

这里有一个不起眼的地方加了一行代码,给GlobalServices容器”吃小灶“

不挖不知道,一挖吓一跳,有一种”柳暗花明又一村“的感觉

哈哈,我就说我要对每个容器做包装,”居心不良“吧;哈哈,这样说太难听了,一句话"还是为了更好的扩展"。

4、没办法了,还得继续挖GlobalServices.CheckServices

这里可以看到框架支持的"基本类型",把这些转化策略都注册到容器中,主要是担心容器注册的服务不够用,把不足的基本类型转化服务都注册上

当然如果容器中已经存在同类型的"服务",这里的注册是会忽略的

同样,就算是这里注册的服务,如果觉得不好用,也可以在外面重新注册并覆盖

还有一点,前面截图可以明显看出"基础服务模块"的基本样子,但是除了”类型转化子模块“外,其他子模块都还没开发出来

 

四、拆分使用

1、字符串转时间测试

以上两个日期字符串我们用来转化为时间,一个成功一个失败

这个例子我想说明两个问题

其一、每个对象转化服务都可以单独使用而且性能最好,省去了复杂的的策略逻辑、容器操作,而且执行逻辑明确

其二、使用特定的服务不便于扩展,如果这样的代码有bug需要替换,每个地方去改,很容易导致遗留问题(没改全)

其三、通用服务都是默认实现,总有局限性,有些特殊问题无法解决(以上调用逻辑和使用全局服务的逻辑一致,也会出现异常),总有特事特办的,只要控制数量还是没有问题的

2、对特殊字符串转转时间测试

以上顺利转化成功,可喜可贺,事实上有的时候特事特办能做到更高的性能或者更好的效果

这里还有一个我要说的就是“面向对象编程”,在我看来很多人都是在做"面向类"编程,因为他们的类型都只有一个实例或者都只能简单的new()来构造,我特别强调类和对象是一对多的关系,一个类可以初始化多个对象,通过不同的字段和属性能达到不同的效果。这样才可以把类和对象用到极致。不是有句名言“程序等于算法加数据结构”。对我而言,每个对象就是一个独立的程序,字段(属性)就是数据结构,定义的方法(函数)就是算法。好的对象是可以独立迁移的。如果使用容器配置管理,我要迁移程序(组件对象)就只要复制对应的配置节点和dll即可。

当然不是说一定非要硬编码,这样的“特事特办”也可以在容器中配置特殊的节点来实现

3、从容器中获取特殊服务对特殊字符串转转时间测试

效果还不错吧,而且也是面向接口的,要扩展也是非常简单

以下是容器配置:

以上是Unity容器配置,能实现类似配置的容器很多,选择也很多,再强调一次逻辑代码没有必要强依赖任何固定的容器

 

五、IFormattable转化为字符串服务

 1、时间格式化的例子

效果不错吧

有人说使用DateTime的ToString()方法传一个字符串参数吗?确实,底层调用的是同一个逻辑。

我们知道DateTime的ToString()可以传一个格式字符串,那这个格式字符串硬编码写死不太好吧,如果哪天我们要的字符串格式使用ToString()无法实现怎么办。ToString(format)是一种转化字符串的方式,但不是唯一的方式。 这就是面向接口的好处,我们依赖的是把一个把DateTime转化为string的服务(接口),甚至是一个把object转化为string的服务。这样我们的服务就更强大,更容易扩展了。

 2、使用容器再做一个上面的例子

效果和前面一样

以下是容器配置:

当然还可以注册为全局服务(使用GlobalServices.Convert就可以调用到,覆盖默认的ToString()),这样就更方便了(但是要慎重,全局格式一定定义的足够通用)

 

六、扩展性演示

1、逻辑空(int转bool)

前面有提到,我是把>0为true的,<=0为false,我一直用的很方便,我叫做"逻辑空",但是ms并不是这样处理的,如果有的团队还是希望按ms的规则怎么办,扩展啊

2、把上例中的注释打开就切换为ms模式(==0为false)的扩展

上面的结果就是ms模式了

3、现在来看一下是怎么扩展的

以上代码很清楚,扩展很简单,获取服务配置的容器,注入服务并覆盖原服务,就完成了扩展

当然如果自己想扩展基础服务中的任何功能都不是问题,只需要实现对应服务接口,如后注册到服务容器中去就可以了

 

七、IConvertible转化功能

1、IConvertible转化int的例子

A:这里演示的就是IConvertible(15种类型)转化为int的部分例子,完全没有问题,调用也很方便

B:有的人可能会说,Ms提供了IConvertible搞定15中类型相互转化,你写那些类型转化完全是重复造轮子,这是没有理解这个框架的思想

     注:在这里使用Convert<IConvertible, int>转化short类型的值(123)是没有使用Convert<short, int>性能好的,不仅仅是类型转化的问题,完全不是一套逻辑

C:这个框架(可扩展)不只是提供某种功能,他要提供高度可扩展性,以便对任意的细分"领域"都可以扩展优化效果和性能

D:特别是性能这块,框架优先获取最细分领域的服务来处理问题,我有一个开发思想,通用工具解决大部分(60%到90%)的问题,有特殊性能或业务需求的单独写代码来支持,这个框架就是对这种思想的强有力支持

     注:这个框架和其他框架有个明显的区别,一般框架是越扩展(打补丁)性能越差,这个框架是相反的,一般情况下是越扩展性能越好(除非扩展进来的服务太差,那还叫扩展吗)

 

八、自定义类型转化

1、继承类型转化试试

木有问题,转化成功

2、取消继承再测

“哦,NO!框架出bug了,这么简单的事情也出错”

不是的,大家好好看这个错误信息的内容,可扩展框架提醒你,需要扩展

3、按提示的扩展IEntityConvert再测

又转化成功了,说明可扩展此言不虚

有人说你这不就是硬编码吗?类型转化这等小事,还要一个个硬编码,还让不让人活了。我还要重复一遍,对于特别性能需要的地方,硬编码是有必要的

而诶硬编码类型转化代码不需要你直接调用,是注入到框架中的,调用的地方没有区别(不知道是调用硬编码实现还是默认实现)

有人说你要我每个类型都硬编码就是不对,现在没有这么干的!确实没有必要,90%以上的类型转化都不需要硬编码,再按提示做另一种扩展

4、扩展IConvert再测

再次转化成功,可以使用这种方式扩展自定义类型的默认转化

有人要挑错了,你以上代码哪是任意类型嘛,明明就只能A转B,其他都转成null了。以上只是个示例,现在开源项目中能实现这样转化的工具很多,我真没必要开发到主框架中,拿来用就可以了

我完全可以拿Automapper(或者EmitMapper,LiteMapper等)封装一下,继承IConvert接口,然后注册到框架中来,这才是本框架的精神(集成和整合),还是那句话,如果特定两个类型转化需要高性能,直接硬编码注册进来,所有调用的地方不用改就达到优化的目的

我也不用再纠结哪个Mapper更好,所有Mapper都封装,爱用哪个就注册哪个(当然不建议同一个应用程序使用两个以上的Mapper)

 

类型转化基础服务基本介绍完了,还有遗漏的我再补(大块应该都差不多了),后面的精力将转为其他模块的开发和介绍。有什么建议和意见请回复,谢谢。

posted on 2016-04-27 21:02  xiangji  阅读(1507)  评论(12编辑  收藏  举报

导航