简介

泛型参考资料烂大街,基本资料不再赘述,比如泛型接口/委托/方法的使用,逆变与协变。

泛型好处有如下几点

  1. 代码重用

    算法重用,只需要预先定义好算法,排序,搜索,交换,比较等。任何类型都可以用同一套逻辑
  2. 类型安全

    编译器保证不会将int传给string
  3. 简单清晰

    减少了类型转换代码
  4. 性能更强

    减少装箱/拆箱,泛型算法更优异。

为什么说泛型性能更强?

主要在于装箱带来的托管堆分配问题以及性能损失

  1. 值类型装箱会额外占用内存
            var a = new List<int>()
{
1,2, 3, 4
};
var b = new ArrayList()
{
1,2,3,4
};

变量a:72kb



变量b:184kb

  1. 装箱/拆箱会消耗额外的CPU
	public void ArrayTest()
{
Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Start();
ArrayList arrayList = new ArrayList();
for (int i = 0; i < 10000000; i++)
{
arrayList.Add(i);
_ = (int)arrayList[i];
}
stopwatch.Stop();
Console.WriteLine($"array time is {stopwatch.ElapsedMilliseconds}");
} public void ListTest()
{
Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Start();
List<int> list = new List<int>();
for (int i = 0; i < 10000000; i++)
{
list.Add(i);
_ = list[i];
}
stopwatch.Stop();
Console.WriteLine($"list time is {stopwatch.ElapsedMilliseconds}");
}

如此巨大的差异,无疑会造成GC的管理成本增加以及额外的CPU消耗。

思考一个问题,如果是引用类型的实参。差距还会如此之大吗?

如果差距不大,那我们使用泛型的理由又是什么呢?

开放/封闭类型

CLR中有多种类型对象 ,比如引用类型,值类型,接口类型和委托类型,以及泛型类型。

根据创建行为,他们又被分为开放类型/封闭类型

为什么要说到这个? 泛型的一个有优点就是代码复用,只要定义好算法。剩下的只要往里填就好了。比如List<>开放给任意实参,大家都可以复用同一套算法。

举个例子

  1. 开放类型是指类型参数尚未被指定,他们不能被实例化 List<>,Dictionary<,>,interface 。它们只是搭建好了基础框架,开放不同的实参
            Type it = typeof(ITest);
Activator.CreateInstance(it);//创建失败 Type di = typeof(Dictionary<,>);
Activator.CreateInstance(di);//创建失败
  1. 封闭类型是指类型已经被指定,是可以被实例化 List<string>,String 就是封闭类型。它们只接受特定含义的实参
            Type li = typeof(List<string>);
Activator.CreateInstance(li);//创建成功

代码爆炸

当使用泛型时,在JIT编译阶段,CLR会获取泛型的IL,再T对应的实参替换,生成合适的本机代码。

但这么做有一个缺点,要为每一种不同的泛型类型/方法组合生成,各种各种的本机代码。这将明显增加程序的Assembly,从而损害性能

CLR为了缓解该现象,做了一个特殊的优化:共享方法体

  1. 相同类型实参,共用一套方法

    如果一个Assembly中使用了List<Struct>另外一个Assembly也使用了List<Struct>

    那么CLR只会生成一套本机代码。

  2. 引用类型实参,共用一套方法

    List<String>与List<Stream> 实参都是引用类型,它们的值都是托管堆上的指针引用。因此CLR对指针都可以用同一套方式来操作

    值类型就不行了,比如int与long. 一个占用4字节,一个占用8字节。占用的内存不长不一样,导致无法用同一套逻辑来复用

眼见为实1

示例代码
    internal class Program
{
static void Main(string[] args)
{
var a = new Test<string>();
var b = new Test<Stream>(); Debugger.Break();
}
} public class Test<T>
{
public void Add(T value)
{ }
public void Remove(T value)
{ }
}

变量a:

变量b

仔细观察发现,它们的EEClass完全一致,它们的Add/Remove方法的MethodDesc也完全一直。这印证了上面的说法,引用类型实参引用同一套方法。

眼见为实2

点击查看代码
    internal class Program
{
static void Main(string[] args)
{
var a = new Test<int>();
var b = new Test<long>();
var c = new Test<MyStruct>(); Debugger.Break();
}
} public class Test<T>
{
public void Add(T value)
{ }
public void Remove(T value)
{ }
} public struct MyStruct
{
public int Age;
}

我们再把引用类型换为值类型,再看看它们的方法表。

变量a:



变量b:



变量c:

一眼就能看出,它们的MethodDesc完全不一样。这说明在Assembly中。CLR为泛型生成了3套方法。

细心的朋友可能会发现,引用类型的实参变成了一个叫System.__Canon的类型。CLR 内部使用 System.__Canon 来给所有的引用类型做“占位符”使用

有兴趣的小伙伴可以参考它的源码:coreclr\System.Private.CoreLib\src\System__Canon.cs

为什么值类型无法共用同一套方法?

其实很好理解,引用类型的指针长度是固定的(32位4byte,64位8byte),而值类型的长度不一样。导致值类型生成的底层汇编无法统一处理。因此值类型无法复用同一套方法。

眼见为实

点击查看代码
    internal class Program
{
static void Main(string[] args)
{
var a = new Test<int>();
a.Add(1);
var b = new Test<long>();
b.Add(1); var c = new Test<string>();
c.Add("");
var d = new Test<Stream>();
d.Add(null); Debugger.Break();
}
} public class Test<T>
{
public void Add(T value)
{
var s = value;
}
public void Remove(T value)
{ }
}
//变量a
00007FFBAF7B7435 mov eax,dword ptr [rbp+58h]
00007FFBAF7B7438 mov dword ptr [rbp+2Ch],eax //int 类型步长4 2ch //变量b
00007FFBAF7B7FD7 mov rax,qword ptr [rbp+58h]
00007FFBAF7B7FDB mov qword ptr [rbp+28h],rax //long 类型步长8 28h 汇编不一致 //变量c
00007FFBAF7B8087 mov rax,qword ptr [rbp+58h]
00007FFBAF7B808B mov qword ptr [rbp+28h],rax // 28h //变量d
00007FFBAF7B8087 mov rax,qword ptr [rbp+58h]
00007FFBAF7B808B mov qword ptr [rbp+28h],rax // 28h 引用类型地址步长一致,汇编也一致。

泛型的数学计算

在.NET 7之前,如果我们要利用泛型进行数学运算。是无法实现的。只能通过dynamic来曲线救国

.NET 7中,引入了新的数学相关泛型接口,并提供了接口的默认实现。

https://learn.microsoft.com/zh-cn/dotnet/standard/generics/math

数学计算接口的底层实现

C#层:

相加的操作主要靠IAdditionOperators接口。

IL层:

+操作符被JIT编译成了op_Addition抽象方法

对于int来说,会调用int的实现

System.Int32.System.Numerics.IAdditionOperators

对于long来说,会调用long的实现

System.Int64.System.Numerics.IAdditionOperators

从原理上来说很简单,BCL实现了基本值类型的所有+-*/操作,只要在泛型中做好约束,JIT会自动调用相应的实现。

结论

一路无话,无非打打杀杀。

泛型,用就完事了。就是要稍微注意(硬盘比程序员便宜多了)值类型泛型造成的代码爆炸。

.NET Core 泛型底层原理浅谈的更多相关文章

  1. Java线上问题排查神器Arthas快速上手与原理浅谈

    前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...

  2. CSRF漏洞原理浅谈

    CSRF漏洞原理浅谈 By : Mirror王宇阳 E-mail : mirrorwangyuyang@gmail.com 笔者并未深挖过CSRF,内容居多是参考<Web安全深度剖析>.& ...

  3. 如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

    在日常业务代码开发中,我们经常接触到AOP,比如熟知的Spring AOP.我们用它来做业务切面,比如登录校验,日志记录,性能监控,全局过滤器等.但Spring AOP有一个局限性,并不是所有的类都托 ...

  4. JAVA CAS原理浅谈

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  5. CAS+SSO原理浅谈

    http://www.cnblogs.com/yonsin/archive/2009/08/29/1556423.htmlSSO 是一个非常大的主题,我对这个主题有着深深的感受,自从广州 UserGr ...

  6. php模板原理PHP模板引擎smarty模板原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  7. PHP的模板引擎smarty原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  8. Docker 基础底层架构浅谈

    docker学习过程中,免不了需要学习下docker的底层技术,今天我们来记录下docker的底层架构吧! 从上图我们可以看到,docker依赖于linux内核的三个基本技术:namespaces.C ...

  9. Java中的SPI原理浅谈

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...

  10. JDK source 之 LinkedHashMap原理浅谈

    注:本文参考JDK1.7.0_45源码. LinkedHashMap是基于HashMap实现的数据结构,与HashMap主要的不同为每个Entry是使用双向链表实现的,并且提供了根据访问顺序进行排序的 ...

随机推荐

  1. 一次生产环境mysql迁移操作(一)数据归档

    一次生产环境mysql迁移操作(一)数据归档 一次生产环境mysql迁移操作(二)mysql空间释放(碎片整理) 背景 在项目过程中我们经常要对数据库进行迁移.归档.拆分等等操作,现在描述下几种方案 ...

  2. Kummer 定理

    \(n!\) 中含素数 \(p\) 的幂次为 \(\displaystyle\sum_{i=1}\lfloor\frac{n}{p^{i}}\rfloor\) Kummer 定理:\({n+m\cho ...

  3. ubuntu 16.04 安装Python3.8虚拟环境

    virtualenv为应用提供了隔离的Python运行环境,可以解决不同应用间多版本的冲突问题. virtualenv会把用户指定版本的python复制到虚拟环境下,并修改相关的环境变量,使得pyth ...

  4. 微信小程序中使用Echarts展示折线图

    效果图 主要实现的功能输入地区和频次查询油价的调整消息 1.从echarts-for-weixin官网下载文件 2.项目中引入echarts 将整个文件夹放在项目pages同级的目录下面 import ...

  5. 八,SpringBoot Web 开发访问静态资源(附+详细源码剖析)

    八,SpringBoot Web 开发访问静态资源(附+详细源码剖析) @ 目录 八,SpringBoot Web 开发访问静态资源(附+详细源码剖析) 1. 基本介绍 2. 快速入门 2.1 准备工 ...

  6. 合合信息扫描全能王亮相静安区3·15活动,AI扫描带来绿色消费新体验

    保护消费者的合法权益,是全社会的共同责任.为优化消费环境.促进品质消费高地建设,打造安全优质和谐的消费环境,上海静安区消保委于3月15日举办静安区2024年"3·15"国际消费者权 ...

  7. Angular 18+ 高级教程 – Angular CLI

    前言 这篇会列出我开发中常用的 command. 并给予一些简单的说明 Command Format 先了解一下几个简单的 command 格式: 缩写 shortform 这个是完整版 ng gen ...

  8. EF Core – 继承 Inheritance

    前言 继承是面向对象里的概念. 关系数据库只有一对一, 一对多这类关系, 并没有 "继承" 关系的概念. 所以 ORM (Object–relational mapping) 就需 ...

  9. Bit, Byte, ASCII, Unicode, UTF, Base64

    前言 做项目偶尔会接触到 stream 这个感念,不管是 memory stream 还是 file stream,它们又会提到 bytes. 还有像 Identity – 安全基础知识 中提到的 S ...

  10. C# – 6.0, 7.0, 8.0, 9.0 总结

    前言 C# 这几年改了好几个版本, 多了许多语法糖,还带有 JavaScript / TypeScript 的味道了. 我觉得随着 blazor 的发展 (想取代前端开发 ?) 那 C# 必然需要更多 ...