C# 托管与非托管类型 堆和栈 值类型与引用类型 装箱与拆箱
阅读原文时间:2023年07月08日阅读:1

1.托管类型

托管类型包括 引用类型 以及 包含有引用类型或托管类型成员的结构。

  • 引用类型
  • 含引用类型或托管类型成员(字段、自动实现 get 访问器的属性)的结构

// 托管结构。 (这里的struct 包含了引用类型字段)
public struct Foo {
public string Name; // 包含引用类型字段。
public string Bar { get; private set; } // 包含自动实现 get 访问器的引用类型属性。
}

public struct Bar {
public Foo Foo; // 包含托管类型字段(托管结构)。
}

托管需要由GC自动释放

2.非托管类型

如果某个类型是以下类型之一,则它是非托管类型 :

  • sbytebyteshortushortintuintlongulongcharfloatdoubledecimal 或 bool
  • 任何枚举类型
  • 任何指针类型
  • 任何用户定义的 struct 类型,只包含非托管类型的字段

非托管需要自己手动释放  可以继承IDispose  using等

堆和栈:程序运行时的内存区域

我们把内存分为堆空间和栈空间。

栈空间比较小,但是读取速度快

堆空间比较大,但是读取速度慢

1.堆(先进后出)

栈的特征:

数据只能从栈的顶端插入和删除

把数据放入栈顶称为入栈(push)

从栈顶删除数据称为出栈(pop)

2.堆

堆是一块内存区域,与栈不同,堆里的内存能够以任意顺序存入和移除

栈比堆快 值类型快,值类型和引用类型变量本身在栈中分配内存,引用类型的实例在堆中分配内存

类型被分为两种:值类型(结构类型,枚举类型,byte,short,int,long,float,double,decimal,char,bool 和 struct )和引用类型(string ,object,接口,类,字符串,数组,委托)。

1)值类型只需要一段单独的内存,用于存储实际的数据,(如果值类型是在方法内部创建,则跟随方法入栈,分配到栈上存储。如果值类型是引用类型的成员变量,则跟随引用类型,存储在堆上。

2)引用类型需要两段内存 (一块空间分配在堆上,存储引用类型本身的数据,另一个块空间分配在栈上,存储对堆上数据的引用(实际上存储的堆上的内存地址,也就是指针)。)

注意: 但我们使用引用类型赋值时,其实是赋值的引用类型的引用,如果数组是一个值类型的数组,那么数组中直接存储值,如果是一个引用类型的数组(数组中存储的是引用类型),那么数组中存储的是引用(内存地址)。

  • 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
  • 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
  • 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
  • 引用类型的对象总是在进程堆中分配(动态分配)

相同点:

引用类型可以实现接口,值类型当中的结构体也可以实现接口;引用类型和值类型都继承自System.Object类。

不同点:

几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

值类型→引用类型(装箱),引用类型→值类型(拆箱)

//装箱 boxing
int i = 3 ; //分配在栈上
object o = i ;//隐式装箱操作,int i 在堆上
object b = (object)i ; //显示装箱操作
//拆箱 unboxing
int j = (int) o ;//显示拆箱(将对象o拆箱为int类型)

int k = b ;//error!!, 不能隐式拆箱

下面来看看这个例子:

int i=0;
System.Object obj=i;
Console.WriteLine(i+","+(int)obj);

其中共发生了3次装箱和一次拆箱!^_^,看出来了吧?!
第一次是将i装箱,第2次是输出的时候将i转换成string类型,而string类型为引用类型,即又是装箱,第三次装箱就是(int)obj的转换成string类型,装箱!
拆箱就是(int)obj,将obj拆箱!!