注意:未经许可,本系列禁止转载。

本文所介绍的技巧,是我在研究泛型开发不久就发现并成功运用的技巧。这个技巧是突破.NET泛型限制,达到“看上去很美”境界的法宝。当然本方法也存在重大缺陷,后面我会逐一介绍。本文同时使用VB和C#语法,以下是泛型方面VB和C#的语法小小对照:

VB C#
Identifier(Of T) Identifier<T>
Identifier(Of T As C) Identifier<T> where T : C

上次我们介绍了约束模型的缺陷和使用外部辅助类代替约束的手法。现在我们继续研究该手法。如上次所说,基本数值类型Integer, Long和Double等并没有实现什么公共的接口以实现普通加减乘除等运算;在泛型类和方法中,类型参数的变量之间也不允许使用+、-等运算符,所以我们别无选择,只能使用外部辅助类。以下就是我编写的辅助类Calculator。他是这样工作的:

Function Add(Of T)(a As T, b As T)As T
    Return Calculator(Of T).Default.Add(a, b)
End Function

'在另一个方法中
Dim c As Integer = Add(1, 2)
Dim d As Double = Add(1.5, 888.0)

我们看到,Calculator(Of T).Default能够在T确定为Integer或Double的时候分别给出正确的整数相加及浮点数相加运算。.NET泛型本身是缺乏这种编译时类型选择功能的,也就是说,无法根据类型参数T的不同自动选择不同的代码执行。那么是不是只能用运行时If语句加类型判断了呢?如果是那样,运行时效率必然难以保证,同时也失去了使用泛型的意义。所以我们得求助一个工具——类型字典。

“类型字典”这个词是我根据其特性杜撰的,它其实利用到.NET泛型很重要的一个特性:泛型类的静态字段。如果有一个泛型类型A(Of T),那么它的每一个封闭构造类型(Closed Constructed Type)独享一组静态字段的取值。也就是说,假设X是A(Of T)的静态字段(VB中的Shared或C#中的static),那么A(Of Integer).X与A(Of String).X是互相独立的两个字段,改变一个的值不会影响到另一个。这样就给我们提供了一个极为便利的条件,我们可以利用泛型类型的静态字段,为类型参数的每一组封闭取值(封闭的意思是所有取值为非泛型类型或泛型封闭构造类型)保存一个字段的值。简单地说,这就相当于以类型作为关键字,建立了一个字典,其值则是一个对象。以下为C#语法的一个小小的例子:

class TypeDict<T>
{
    public static int Value;
}

//在代码中,我们可以为每个封闭类型保存一个int
//这相当于一个 类型到int 的字典
TypeDict<int>.Value = 1;
TypeDict<string>.Value = 2;
TypeDict<List<int>>.Value = 3;
TypeDict<List<double>>.Value = 4;

从类型查询到其相应的值,只有访问一个静态字段的代价,可谓极低。下面我们就利用这个优势,实现根据类型参数的不同,自动给出适合该类型的算术运算功能。我们统称这种类型参数取不同取值,类的功能就自动变化的功能为type traits,意为“类型特征”。.NET泛型不支持特化,因此不能做编译期选择的type trait,只能将这个过程推迟到运行期。为了减少运行期的消耗,我们采用首次运行进行类型判断,然后用类型字典保存判断结果的手法,我称之为首次缓存的拟type traits。下面用四则运算为例介绍做法:为了简化,我们现在只考虑四则运算功能中的加法。以下是在同类型上实现加法的接口:

Public Interface ICalculator(Of T)
    Function Add(a As T, b As T)As T
    
End Interface

我们要给ICalculator接口提供一个具有type trait功能的默认实现,首先声明一个类型:

Public MustInherit Class Calculator(Of T)
    Implements ICalculator(Of T)

Public MustOverride Function Add(a As T, b As T) As T _
        Implements ICalculator(Of T).Add
    
End Class

注意这是一个抽象类,我们还没有写完他,但当前要做的事情是,为Integer,Double和T(代表未知类型)编写三个特化子类,分别封装Integer, Double和未知类型的加法。

Class IntegerCalc
    Inherits Calculator(Of Integer)

Public Overrides Function Add(ByVal a As Integer, ByVal b As Integer) As Integer
        Return a + b
    End Function
End Class

Class DoubleCalc
    Inherits Calculator(Of Double)

Public Overrides Function Add(ByVal a As Double, ByVal b As Double) As Double
        Return a + b
    End Function
End Class

Class ObjectCalc(Of T)
    Inherits Calculator(Of T)

Public Overrides Function Add(ByVal a As T, ByVal b As T) As T
        '后期绑定的加法运算
        'VB的后期绑定支持运算符重载
        Return DirectCast(CObj(a) + CObj(b), T)
    End Function
End Class

给特殊类型的子类编写加法出奇的容易,因为类型变成了已知类型,因此运算符也就能够允许使用。注意我我们给ObjectCalc编写的加法过程使用了后期绑定加法,因此可以支持任何重载+号的类型,但是他的速度要比IntegerCalc和DoubleCalc中的做法慢很多倍。因此在实际代码中,我为.NET中具有算术加法概念的许多类型,如整型,浮点型,Decimal和Nullable类型都做了特殊子类。而ObjectCalc则为那些没有考虑到的类型提供低性能,但仍起作用的最低保证。 下面我们用If语句模拟编译期的类型选择机制,并保存在Calculator(Of T)的静态字段里,做成类型字典。修改Calculator如下

Public MustInherit Class Calculator(Of T)
    Implements ICalculator(Of T)

Public MustOverride Function Add(ByVal a As T, ByVal b As T) As T _
        Implements ICalculator(Of T).Add

'保存类型T的字典值
    Private Shared defaultCalc As Calculator(Of T)

Public Shared ReadOnly Property [Default]() As Calculator(Of T)
        Get
            If defaultCalc Is Nothing Then CreateCalculator()
            Return defaultCalc
        End Get
    End Property

Private Shared Sub CreateCalculator()
        Dim calcObj As Object
        '根据类型进行特殊子类的指派
        If GetType(T).Equals(GetType(Integer)) Then
            calcObj = New IntegerCalc
        ElseIf GetType(T).Equals(GetType(Double)) Then
            calcObj = New DoubleCalc
        Else
            '未知类型
            calcObj = New ObjectCalc(Of T)
        End If
        '强行让编译器认为我们选择类型就是T
        defaultCalc = DirectCast(calcObj, Calculator(Of T))
    End Sub
End Class

下面我们来看看这是如何工作的,假设我们要进行Integer的计算,那么首先写下Calculator(Of Integer).Default,这是第一次运行,所以它调用CreateCalculator方法。这个方法利用GetType运算符(C#的typeof)判断T是否是我们写过的特殊子类实现的类型,并且给静态字段defaultCalc分配一个特殊子类。这时候Default的值其实就是IntegerCalc的实例。当你第二次运行Calculator(Of Integer).Default的时候,缓存在Calculator(Of Integer)类型字典中的IntegerCalc实例会被直接调用,不再会进行第一次的类型判断。也就是说从第二次起,我们Integer运算的加法就可以好像编译期选择的那样,全速执行了。

类型字典+首次缓存的拟type traits可以漂亮地做出自动根据类型进行选择的功能,但是他也不是没有缺陷的。首先是这种类型选择不能轻易的扩展。假如将Calculator的代码做到类库里,那么从外部想给他加入一个特殊类型子类是无法轻易做到的,因为基于If的判断语句不易扩展,改进的做法可能通过一个(普通)字典作登记。或者结合面向对象,不采用这种方法扩充,而由用户自行实现ICalculator,然后取代默认Calculator(这正是.NET Framework和VBF采用的做法)。第二个缺陷是保存在静态字段中的对象不会被清理,这样就会越用越多,占据内存。好在这种用于计算或比较类的对象都很小。

下一次,我们讨论利用反射操纵泛型类型,以达成更巧妙的设计。

泛型技巧系列:类型字典和Type Traits相关推荐

  1. C++11 类型支持之type traits

    文章目录 一.type_traits是什么 二.type_traits通常用来做什么 三.辅助基类 四.类型相关判断信息获取 4.1 判断基础类型类别 4.1 判断组合类型类别 4.3 判断类型的属性 ...

  2. 泛型技巧系列:简单类型选择器

    注意:本系列未经许可,不得转载. 在泛型编程当中,我们对类型的关注大大提高了.有时需要这样的功能:"当类型是A的时候执行这段代码:当类型是B的时候执行另一段代码".就是说,需要针对 ...

  3. XAML实例教程系列 - 类型转换器(Type Converter)

    在XAML中每一个对象元素映射一个实例,而实例属性可以通过特性(Attributes)进行赋值.在实际项目开发中,对象元素的属性值可以是不同数据类型,根据需求不同,经常需要对数据类型进行转换,就需要使 ...

  4. typescript (TS)进阶篇 --- 内置高阶泛型工具类型(Utility Type)

    第一部分 前置内容 关键字 keyof 索引查询 对应任何类型T,keyof T的结果为该类型上所有公有属性key的联合: interface Eg1 {name: string,readonly a ...

  5. type traits

    Type Traits,  类型萃取,这个概念涉及到的内容太多.基本常用的萃取方法可以参考 http://en.cppreference.com/w/cpp/types 这里主要记录一下对函数的萃取技 ...

  6. Android Studio使用技巧系列教程(七)

    尊重劳动成果,转载请注明出处:http://blog.csdn.net/growth58/article/details/47134819 关注新浪微博:@于卫国 邮箱:yuweiguocn@gmai ...

  7. C++ 模板类型萃取技术 traits

    当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案.(类型测试发生在编译期 ...

  8. C++ Type traits

    C++ Type traits by John Maddock and Steve Cleary DDJ 2000/10 译者:陈崴 侯捷注:本文系北京<程序员>杂志 2001/06 的文 ...

  9. 全面理解Python中的类型提示(Type Hints)

    众所周知,Python 是动态类型语言,运行时不需要指定变量类型.这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发 ...

最新文章

  1. 自然语言处理「迷惑行为大赏」,自然语言处理太难难难了!
  2. Parallel Query Bitmap
  3. 智能安防 回家开门进入“看脸”时代
  4. 请求的安全信息不可用或无法显示
  5. 如何用 J-Link 来串口调试?
  6. 光缆弹性模量计算_光缆的制造、种类、施工、选用方法(超全)
  7. 如何有效的为Windows XP减肥
  8. STM32工作笔记0072---UCOSIII在STM32F103上的移植
  9. 容器算法迭代器初识----容器嵌套容器
  10. cvDilate() 图像膨胀
  11. 【路由器】Breed 介绍、刷入和使用
  12. Kubernetes K8S之Ingress详解与示例
  13. 认识计算机拓扑结构图,认识计算机网络拓扑结构
  14. 英文日期和时间表示方法
  15. 西门子数控系统变量刀补输入——使用$TC_DP函数
  16. pytorch动态调整学习率之Poly策略
  17. java毕业设计图书借阅管理系统mybatis+源码+调试部署+系统+数据库+lw
  18. 计算机考试打字题题库6,2018四至六年级电脑测试题
  19. 很不错的免费电影网站中国影视库mdbchina.com
  20. Hack The Boo 2022 CTF题目writeups

热门文章

  1. 您访问的页面不在地球上卡通错误页面源码
  2. .net面试问答(大汇总)
  3. linux上使用ASP
  4. EXEC和sp_executesql的区别
  5. google 图表(chart)
  6. 机器学习降维算法五——KPCA算法
  7. golang搭建微服务遇到的问题(不断更新)
  8. pandas常用函数(更新中)
  9. Linux sed命令高级用法精讲
  10. Linux zip命令:压缩文件或目录