C#泛型底层实现原理

Posted 1Note

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#泛型底层实现原理相关的知识,希望对你有一定的参考价值。

C#泛型底层实现

C#泛型底层实现原理



泛型(generic)是在.net framework2.0的时候出来的新语法,引入了“类型参数”(type parameter)的概念。最直观的感受就是代码编写量少了,同时还具有类型安全,性能高的优点。

先来个例子感受下泛型的性能!


 static void ShowObj(object o) {
} static void ShowGeneric<T>(T t) {
}

然后分别调用上边两个方法1000000000次,比较下各自的时间

 Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 1000000000; i++) { ShowObj(1); } sw.Stop(); long showobj = sw.ElapsedMilliseconds;
sw.Restart(); for (int i = 0; i < 1000000000; i++) { ShowGeneric(1); } sw.Stop(); long showgeneric = sw.ElapsedMilliseconds;
Console.WriteLine("ShowObj方法使用的时间是:{0}", showobj);            Console.WriteLine("ShowGeneric方法使用的时间是:{0}", showgeneric); Console.ReadKey();

最后的结果

ShowObj方法使用的时间是:4397

ShowGeneric方法使用的时间是:2535

这性能差距够明显吧。


很明显,第一个方法性能低的原因是因为传入的参数是一个整型导致发生了“装箱”。


所以说泛型既提供了一个通用类型参数,又避免了拆装箱导致的性能损耗?


还真是。

微软官方文档是这么介绍的:


Generics introduce the concept of type parameters to the .NET Framework, which make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code. For example, by using a generic type parameter T, you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations, as shown here:



真好,鱼与熊掌兼得。



C#泛型底层实现原理

从编译器、MSIL、CLR三个方面来探究泛型的底层实现:

 01 

编译器



编译器:最直接的一点就是.net framework2.0之后编译器支持泛型语法了。类型安全泛型约束也是编译器带来的便利功能。

 02 

MSIL



C#泛型底层实现原理


MSIL也开始支持泛型语法,有兴趣的同学可以用一下反编译工具看一下MSIL。

C#泛型底层实现原理


更重要的一点是metadata增加了泛型参数。


C#泛型底层实现原理

C#泛型底层实现原理


泛型的相关信息都存到了metadata里面了,这样不就可以实现泛型的反射了吗?


 03 

CLR



C#泛型底层实现原理

JIT编译器编译到泛型方法/泛型类时,根据类型参数为值类型还是引用类型又分为2种不同的实现。

C#泛型底层实现原理

值类型:还是以上边的ShowGeneric为例。当我们调用ShowGeneric并且首次传入的参数是int类型的时候,CLR就会为int类型创建一个该方法的int专用模板(仅首次构造泛型类型时):

static void ShowGeneric<int>(int t){}

当我们下次再需要调用ShowGeneric方法且参数为int类型,那么就直接使用之前生成好的模板即可(一次创建多次使用)。

当我们调用ShowGeneric并且传入的参数为其他值类型的时候,CLR都会为其创建模板(有多少种值类型的实现就有多少种值类型的模板)。


这其实挺好理解,就是CLR自己帮我们写了具体的方法嘛!



引用类型:CLR会为每一种值类型生成对应的专用模板,但是引用类型就不同了,引用类型参数的泛型只生成一个通用模板。


注意:我并没有找到相关文档表明引用类型的通用模板是以object类型作为类型参数,仅仅是我个人的推断(我觉得也只能是object了)

static void ShowGeneric<object>(object t){}


当我们传一个引用类型(假设我们有Student这个类)的参数到ShowGeneric方法时,CLR就会拿出ShowGeneric方法的通用模板(如果是首次则会创建),然后把模板里的全部类型参数(object)替换成Student这个具体实现的类。


值得注意的是,引用类型只会生成一个模板,甭管你用哪种引用类型来实现,一替换就完事!


总结:泛型在编译器层面、MSIL层面都仅仅只是声明而已,其真正“实例化”的阶段是在CLR(JIT编译阶段时)。


CLR根据泛型的具体实现的类型参数不同,会生成不同的模板。CLR会为每一种值类型生成其“定制”的专属模板,而对于引用类型则是生成统一的模板再将具体实现的引用类型“套用”进去。


也正是因为CLR底层对泛型的实现,避免了拆装箱对性能的损耗,从而使其和我们自己手写的方法/类型效率基本五五开。

以上是关于C#泛型底层实现原理的主要内容,如果未能解决你的问题,请参考以下文章

理解C#泛型原理

Java泛型 VS C#泛型 (伪泛型 VS 真泛型)

理解C#泛型运作原理

Swift之深入解析“泛型”的底层原理

面试常考问题:Java泛型的底层原理是什么?

聊聊 C# 和 C++ 中的 泛型模板 底层玩法