值类型、引用类型和泛型之因果

多语言

我们先不说主题,先说说CLR支持多语言。 .net有个很是强大的特色,那就是跨语言,支持不少语言,好比C#、J#等。先来个图看一看数组

C# J# VB 等等等 👇 👇 👇 👇 C#编译器 J#编译器 VB编译器 编译器 👇 👇 👇 👇 --------------------------------- | 中间语言 | --------------------------------- 👇 编译(运行) 👇 机器语言 

看到这个图,每一个语言都有本身的编译器,经过第一次编译,编译成中间文件(dll或是exe文件)。在程序运行的时候,再次编译把中间文件编译成机器语言。安全

可是,CLR支持这么多语言不会出为题么?换句话说是为何CLR会支持这么多语言?ruby

就像一个四川人和一个河南人对话,互相的听不懂,可是,他们两我的都说普通话就能交流了。在CLR中,每种语言就至关于各个地方的人们,相互交流困难,可是有了普通话这个规范(CTS(公共语言类型)和CLS(公共语言规范)),就能够说话了。CTS至关于咱们普通话的音节,而CLS至关于普通话的约定的语法。这样子呢,就组成了.net你们庭。在CTS中,就是值类型和引用类型服务器

进程 线程 内存空间

原本想单独整理一下进程和线程的,后来想和堆栈一块儿简单的说更好多线程

在一个应用程序中,会分配一个进程和最大4G的内存空间,这个内存空间会分为4个区,全局数据区、代码区、线程堆栈区、托管堆区。less

(线程是程序运行的最小单位,线程之间是隔离的,在Window下,每一个程序都会有主进程,分配的内容最大为4G)性能

整个项目都是在这个进程运行的。在这个进程中,可能有不少线程,每个线程都会在栈下分配一个1M的托管堆栈的空间。ui

全局数据区:存放着全局变量、静态数据、常量
代码区:存放全部的程序代码
线程堆栈区:存放为运行而分配的局部变量、参数、返回数据、返回地址
托管堆区:自由存储区


--------------------------------------------------------------------------------
| ---------------------------------------- -------------------- | | | ---- --- --- ---- | | | | | | |线| |线| |线| |线| | | | | | | |程| |程| |程| |程| | | | | | | |1 | |2| |3| | 3| | | 托 管 堆 | | | | ---- --- --- ---- | | | | | | (线程数量取决于你的代码) | | | | | | 线 程 堆 栈 | | | | | ---------------------------------------- -------------------- | | | | | | ------------------------------- ----------------------------------- | | | | | | | | | 全 局 数 据 区 | | 代 码 区 | | | | | | | | | | | | | | | ------------------------------- ----------------------------------- | -------------------------------------------------------------------------------- 

值类型与引用类型

C#中,通常状况下值类型存在它申明的地方,引用类型存在托管堆上。值类型转换引用类型叫装箱,引用类型转换值类型叫拆箱lua

值类型

对于值类型的实例,CLR在运行时有两种分配方式: (1) 若是该值类型的实例做为类型中的方法(Method)中的局部变量,则该实例被建立在线程栈上; (2) 若是该值类型的实例做为类型的成员,则该实例做为引用类型(引用类型在GC堆或者LOH上建立)的实例的一部分,被建立在GC堆上 

引用类型

对于引用类型的实例,CLR在运行时也有两种分配方式:
(1) 若是该引用类型的实例的Size<85000Byte,则该实例被建立在GC(Garbage Collection)堆上(当CLR在分配和回收对象时,GC可能会对GC堆进行压缩); (2) 若是该引用类型的实例的Size>=85000byte,则该实例被建立在LOH(Large Object Heap)上(LOH不会被压缩)。 

说到了这里,铺垫也Ok了,下面有一个题: 在不考虑多线程的条件下,定义了个结构StructObj,里面有int类型的变量x;又定义了一个类ClassObj,里面也有int类型的变量x。spa

        ClassObj r1 = new ClassObj();//在堆上分配
        StructObj v1 = new StructObj();//在栈上分配
        r1.x = 5;//根据地址找到引用类型,进行修改
        v1.x = 5;//在栈上修改
        ClassObj r2 = r1;//只复制引用
        StructObj v2 = v1;//在栈上分配空间并复制成员
        v2.x = 6;
        r2.x = 6;

        Console.WriteLine("v2.x=" + v2.x);
        Console.WriteLine("r2.x=" + r2.x);
        Console.WriteLine("v1.x=" + v1.x);
        Console.WriteLine("r1.x=" + r1.x);

        Console.WriteLine(r1.Equals(r2).ToString());
        Console.WriteLine(v1.Equals(v2).ToString());

 

如今的打印结果是什么? 答案是:

v2.x=6 r2.x=6 v1.x=5 r1.x=6 True False 

来个图理解下

线程堆栈                托管堆

        r1-------| v1.x=5 |------R=6(r2从新赋值前这个值为5) r2-------| v2.x=6 

画的比较抽象,r1是引用类型,r1的值存在声明他的地方,在栈上存的是托管堆的地址,赋值r1.x=5,v1.x=5 再次实例化了,StructObj被存储在了他声明的地方,又在栈上开辟了一个空间存储x=6,r2实例化只是在复制了引用,再次赋值的时候会覆盖原先的5,那么r1.x和r2.x值就会相等,说的明白点就是r1和r2就是一个东西。

那么r1就会和r2彻底相等(值,地址等等) 比较值类型只会比较他们的值是否相等,因此为False

要是仍是看不懂,这还能怪我咯(有本事你来打我)

忽然想到一个有意思的问题:为何要分值类型个引用类型?

早在C#出现以前就已经存在了值类型和引用类型。着么说呢,也应该是一种无奈把。在Window下的应用程序,一个应用程序下假设有四个线程,每个线程都会分配一个1M的空间,那么4个线程就是4M的空间。先来了一个1.2M的包存进去了,又来了一个1.2的包存进了,如今来了个3M的包发现不足了,这 是他不会开辟新的空间,可能释放了一个1.2包还存不进去。这是就有内存碎片产生了。若是把因此的数据都当值类型用,线程堆栈区很快就会满了并且丢出不少内存碎片拖慢服务器速度。那么我将大的数据存在托管堆上,在线程堆栈上保存一个我在托管堆存放数据的地址,而值类型是一些很小的数据,而且是线程运行的时候才能使用的,因此放在了线程堆栈上。而引用类型里面可能有不少东西,数据比较大,就存在了托管堆上

如今再来谈装箱和拆箱

装修是将值类型转换成object,再将包装后的对象存储在堆上的一个过程。而拆箱是从object到值类型的转换,先检查对象,确保他是给定值类型的一个装箱值后。将值复制到新的值类型中,释放当前对象 由此看来,不论是装箱仍是拆箱,都须要CPU大量的计算

数组、ArrayList、泛型

数组被大部分语言支持,其优势在因而连续存储的,因此他的索引速度是很是的快,并且赋值与修改元素也很简单,访问速度较快。缺点是数组是定长的,在建立的时候,就须要知道其大小。并且插入数据和删除很费劲。 相对于数组来讲,ArrayList解决了数据的问题,能够很灵活的插入数据和删除。因为ArrayList对数据类型并不严格要求,在添加的时候使用object类型来容纳添加的对象,这里会有装箱拆箱的操做,照成了性能的损失。

为了解决这种问题,微软推出了泛型集合List,以下

List<int> list=new List<int>();

 

能够看到,在定义泛型集合的时候已经指定了类型。

由于泛型指定了类型,因此在存取的时候值限制于限定的类型内。这样就避免看装箱拆箱所消耗的性能问题,又同时提升了安全性

而后再说说泛型,在这里须要特别说明的是,泛型不能直接的说是值类型仍是引用类型,而是指定类型.

在工做总,经常会遇到泛型的代码,我在操做的时候不须要在意你是什么类型,你给我传过来是什么类型我就处理什么类型,或是你须要什么类型我给你什么类型。好比:

public class Www<T> where T: ClassObjP
{
    public Www()
    {
    }
}

 

在这里,T表示任意类型,可是,T只是个符号,不是关键字,写A、B、Z等等等等均可以。ClassObjP表示是约定的类,约定制定类的范围(这类以及这个类的儿子),遵循历史替换原则的类(原谅本身表达能力)

好了,总结的够多了。如今最后在拓展一下

协变 逆变

上面刚刚在约束里过了一下,也提了下历史替换原则,而在工做中,咱们又很小的可能会遇到一些由于没有遵循里氏替换原则的代码而报错,这里就可能用获得协变逆变

从使用上来讲:协变是子类转父类,只能用在输出参数out;逆变是父类转子类,只能用在输入参数

在用法上呢,可使用协变,可使用逆变,也能够协变和逆变辅助使用

相关文章
相关标签/搜索