程序员常用的泛型机制究竟怎么玩?


作者 | 朱钢,责编 | 郭芮

头图 | CSDN 下载自东方IC

出品 | CSDN(ID:CSDNnews)

开发人员会经常用到泛型,并且大部分开发人员都会很好的使用泛型。泛型需要底层运行时的支持,泛型类中类型参数变成了元数据,在运行时需要时会利用她们来构造恰当的类,因此泛型支持继承、多态和封装。简单地说了一下泛型深层的知识,下面我们来看看泛型的内部机制到底是什么。

CIL 表示泛型

泛型类在经过编译后其实和普通类编译后的结果并没什么太大的出入,结果都是 CIL 和元数据,唯一的区别是 CIL 会用特殊的标记来表示该编译结果为泛型类的编译结果,我们通过一个例子来看一下。

public class Demo<T> where T:IComparable


{


    //more code


    T[] item;


    //more code


}

上述代码中定义了一个 Demo 泛型类,它包含一个类型参数。经过变异后最终生成的参数化 CIL 代码如下:

.class private auto ansi beforefieldinit Demo'1 <[mscorlib]System.IComparable)T> extends [mscorlib]System.Object


{


    //more CIL code


    .field private !0[ ] items


    //more CIL code


}

在上述参数化 CIL 代码中我们看到在 Demo 后面出现了一个 '1 ,它叫做元数也就是参数数量,表示声明泛型类时要求的类型实参的数量,上面的这个例子因为只一个类型参数因此他需要的类型实参的数量就是 1 个,如果是 Demo<TKey,TValue,Tother> 这样的泛型类的话,生成的参数化 CIL 代码中的类型实参数量就是 3,表示为 '3 。在参数化 CIL 代码中显示了施加在类上的约束,既 [mscorlib]System.IComparable 。同时 CIL 代码修改了定义的 T 类型数组声明,使用 ! 包含一个类型参数。除了上说所说的三点之外,生成的其他 CIL 代码和普通类生成的代码没什么区别。

值类型泛型实例化

当用值类型作为类型参数构造泛型类型时为了创建具体化的泛型类型,运行时会在 CIL 合适的位置放上指定的类型参数。

当我们首次调用上一小节中定义的泛型类 Demo 并将 int 作为类型实参传递给 Demo 时,运行时会生成 Demo 的具体化版本,并使用 int 替换类型参数。这样每次使用 Demo<int> 的时候运行时都会重用已经生成的具体化类 Demo<int> 。这里需要注意的是生成的具体化类只针对类型实参是 int 的情况下,如果这时又定义了一个以 float 为类型实参的 Demo 时,运行时将会再生成泛型类型的另一个版本。

使用值类型泛型实例能避免代码的转换和装箱操作进而提高性能,但是在使用过程中也需要根据具体代码、具体情况和具体项目来判断值类型泛型是否该使用该怎么使用。

Tip:运行时会为每个新的只类型参数创建一个具体的泛型类型。

引用类型泛型实例化

泛型实例在使用引用类型作为类型实参的时候和值类型有点不同。在构造泛型类型时运行时会在 CIL 代码中使用 object 引用替换类型参数来创建具体化泛型类型,当每次使用引用类型参数实例化构造好的类型时,运行时都会重用已经生成好的版本,这里需要注意的是如果提供的引用类型和构造泛型类型时的引用类型不同,它依然会使用已经生成好的那个版本。下面看一个例子:

Demo<User> userDemo;


Demo<Student> studentDemo;

首先当代码运行到第一行时,运行时会生成 Demo 的具体化版本,CIL 不会存储 User 作为制定的数据类型,而是存储 object 引用。然后代码运行到第二行时,虽然 Student 和 User 的引用不同但是 CIL 不会为 Student 创建一个新的具体化版本,而是实例化前面给予 object 引用的 Demo 的实例。当然,为了确保类型安全, CIL 会分配给 Order 类型一个内存区域来替换类型参数的每个 object 引用导致向这个内存区域。使用引用类型泛型的好处是编译器将它创建的具体化类压缩至 1 个,因此大大减少了代码量,提高了代码的性能。

这里需要注意,引用类型类型参数在发生变化时运行时使用相同内部泛型类型定义,但是如果出现如下的情况,那么就不会适用相同的内部泛型类型定义。

Demo<int,User> userDemo;


Demo<long,User> userLongDemo;


Demo<Guid,User> userGuidDemo;

在上述代码中类型参数包含值类型,这时就不会使用相同的内部泛型类型定义,而是为每个 Demo 创建不同的内部类型定义。

小结

这篇文章虽然简短,但是充分讲解了泛型机制的内部原理以及相关注意事项。.NET 中的泛型可以说是最好的,在 Java 中使用泛型完全是在编译器中实现的而不是在 JVM 中实现的,虽然这样可以预防使用泛型而分发新的 JVM ,但是由于它不区分值类型和引用类型,所以执行效率底以及影响到反射部分功能的使用。

作者:朱钢,.NET高级开发工程师,7年一线开发经验,参与过电子政务系统和AI客服系统的开发,以及互联网招聘网站的架构设计,目前就职于北京恒创融慧科技发展有限公司。

声明:本文系作者原创投稿,未经允许请勿转载。

【END】

更多精彩推荐

百年 IBM 终于 All In 人工智能和混合云!

☞微软、苹果、谷歌、三星……这些区块链中的科技巨头原来已经做了这么多事!

☞斩获GitHub 2000+ Star,阿里云开源的 Alink 机器学习平台如何跑赢双11数据“博弈”?| AI 技术生态论

☞微软为一人收购一公司?破解索尼程序、写黑客小说,看他彪悍的程序人生!

机器学习项目模板:ML项目的6个基本步骤

☞IBM、微软、苹果、谷歌、三星……这些区块链中的科技巨头原来已经做了这么多事!

资深程序员总结:分析Linux进程的6个方法,我全都告诉你

今日福利:评论区留言入选,可获得价值299元的「2020 AI开发者万人大会」在线直播门票一张。  快来动动手指,写下你想说的话吧。

点击阅读原文,精彩继续!

你点的每个“在看”,我都认真当成了喜欢

发布了1940 篇原创文章 · 获赞 4万+ · 访问量 1813万+

猜你喜欢

转载自blog.csdn.net/csdnnews/article/details/105424407
今日推荐