注意,分配内存只有102_400_024字节,比我们预估的102_400_000只多了24个字节。这是因为数组也是引用类型,引用类型需要至少24个字节。
比较 运行时间 时间比 分配内存 内存比值类型 32 / 102_400_024 /
引用类型 8_681 271.28x 3_440_000_304 33.59x
在这个示例中,将引用类型改成值类型需要多出271倍的时间,和33倍的内存占用。
重新审视值类型值类型这么好,为什么不全改用值类型呢?
值类型的优点,恰恰也是值类型的缺点,值类型赋值时是复制值,而不是复制引用,而当值比较大时,复制值非常昂贵。
在远古时代,甚至是没有动态内存分配的,所以世界上只有值类型。那时为了减少值类型复制,会用变量来保存对象的内存位置,可以说是最早的指针了。
在近代的的C里,除了值类型,还加入了指向动态分配的值类型的指针。其中指针基本可以与引用类型进行类比:
✔指针和引用类型的引用,都指向真实的对象内存位置
❌动态分配的内存需要手动删除,引用类型会自动GC回收
❌指针指向的内存位置不会变,引用类型指向的内存位置会随着GC的内存压缩而产生变化,可用fixed关键字临时禁止内存压缩
❌指针指向的内存没有额外消耗,引用类型需要分配至少24字节的堆内存
C++为了解决这个问题,也是卯足了劲。先是加入了值引用运算符 &,而后又发布了一版又一版的“智能”指针,如auto_ptr/shared_ptr/unique_ptr。但这些“智能”指针都需要提前了解它的使用场景,如:
有对象所有权还是没有对象所有权?
线程安全还是不安全?
能否用于赋值?
而且库与库之前的版本多样,不统一,还影响开发的心情。
所以引用类型的优势就出来了,不用关心对象的所有权,不用关心线程安全,不用关心赋值问题,而且最重要的,还不用关心值类型复制的性能问题。
C#中的值类型支持引用类型是如此好,以至于平时完全不需要创建值类型,就能完成任务了。但为什么值类型仍然还是这么重要呢?就是因为一旦涉及底层,性能关键型的服务器、游戏引擎等等,都需要关心内存分配,都需要使用值类型。
因为只有C#才能不依赖于C/C++等“本机语言”,就可写出性能关键型应用程序。
C#因为有这些和值类型的特性,导致与其它语言(C/C++)相比时完全不虚:
首先,C#可以写自定义值类型
C# 7.0 值类型Task(ValueTask):大量异步请求,如读取流时,可以节省堆内存分配和GC 点击查看
C# 7.0 ref返回值/本地变量引用:避免了大值类型内存大量复制的开销(有点像C++的&关键字了)
C# 7.0 Span<T>和Memory<T>,简化了ref引用的代码,甚至让foreach循环都可以操作修改值类型了 点击查看
C# 7.2 加入in修饰符和其它修饰符,相当于C++中的const TypeName&
C# 8.0 - Preview 5 可Dispose的ref struct,值类型也能使用Dispose模式了
ASP.NET Core曾使用Libuv(基于C语言)作为内部传输层,但从ASP.NET Core 2.1之后,换成了用.NET。
最后的话开发经常拿C#与同样开发Web应用的其它语言作比较,但由于缺乏对值类型的支持,这些语言没办法与C#相比。
其中Java还暂不支持自定义值类型。
推荐书籍:《C#从现象到本质》(郝亦非 著)