C#值类型和引用类型

.NET类型主要分为值类型和引用类型。

装箱和拆箱

装箱

值类型隐式转换成object类型或到此值类型所实现的任何接口类型,叫装箱(box)。 装箱会在堆中分配一个对象实例,并将值复制到新的对象中。

int i = 100;
object obj = i;

装箱有不少的性能损失,而且装箱后的值类型也需进行垃圾回收。

拆箱

将已经装箱的引用类型转换回值类型,叫做拆箱(unbox)。

int i = 100;
object obj = i;
int j = (int)obj;

拆箱只能将其转换成原始类型,而不能是其他类型,即使被装箱类型和要拆箱类型之间支持隐式转换。

short i = 100;
object obj = i;
int j = (int)obj;//运行时错误
int k = (short)obj;//ok

装箱和泛型

.NET的泛型是真正的泛型,会在运行时为不同泛型参数生成不同的代码,因此使用值类型作为泛型参数,不会导致装箱,拆箱。

装箱和接口

如果一个结构实现了接口,那么在通过接口来访问类型时,会产生装箱。

public interface IPoint
{
    int X {get;}
    int Y {get;}
}

public struct Point :IPoint
{
    public int X {get;}
    public int Y {get;}

    public Point(int x,int y){
        X = x;
    }
}

Point p = new Point(3,4);
IPoint p2 = p;//这里发生了装箱

存储方式

值类型变量存储的是值本身

int a = 10

a这个变量存储区就是10

int b = a

b这个变量存储区就是a变量存储区的值,即10

在局部变量中,值类型一般是存储在栈这个数据结构中。栈是系统为程序分配的内存,不需要程序员管理。

在作为字段时,值类型的存储位置和定义类型和使用位置有关,可以是栈,也可能是托管堆(比如作为引用类型的字段时)。

引用类型变量存储的是引用

引用保存了一个内存地址的值,该内存地址所保存的是对象本身。

string s = "hello";

变量s保存的是"hello"的一个引用,引用保存的是一个内存地址的值,该内存地址所在位置保存的值才是"hello"

一般我们说变量s保存了字符串"hello",这是建立与交流双方都了解引用的前提下的。严谨的说法是:变量s保存了对字符串"hello"的引用。

string t = s;

t存的是s"hello"的一个引用(地址),因此,t也是对"hello的引用(地址)。 "hello"在内存中只存了一份,"hello"的引用(地址)在内存中存了两份。

引用类型存储在托管堆中,程序员只需要申请,不需要管理。 在引用不被使用后,垃圾回收会在合适的时机进行清理。

为何要有值类型和引用类型的区别

值类型的特点是赋值的时候自身进行复制,这就要求类型自身不能过大,否则复制成本(内存和时间消耗)就会提高。优点是不需要进行垃圾回收。

引用类型赋值复制的是引用(地址),复制的时候内容本身没有复制,而是复制一个地址,优点是复制成本总是很低。缺点就是需要进行垃圾回收。

这两种类型的存在就是为了更好的平衡空间和时间。占用空间小而且固定的类型,复制成本低,定义成值类型,免除垃圾回收的时间。对于占用空间大的,定义为引用类型,节省复制成本,但缺点是需要垃圾回收。

在C++中,内存分配在哪里,内存何时回收,是由程序员自己决定的,因此也就没有了值类型和引用类型之分了。与其对比,值类型就相当于使用类型直接定义的变量,引用类型就相当于使用new进行分配的变量,相当于指针。

C#中绝大多数类型都是引用类型,这是由于值类型不能够被继承,不能参与继承体系。

垃圾回收

引用变量引用的值总是被分配在托管堆上。当不再被引用的时候,会在合适的时候被清理。

引用变量自身分配在栈上。

值类型分配位置和其使用位置有关。

当值类型作为局部变量时,分配在栈上,调用结束后被系统回收;作为字段时,结构的值类型字段和取决于值类型的使用位置,类的字段分配在托管堆上。

struct Point
{
    public int X{get;set;}
    public int Y{get;set;}
}

class Line
{
    public Point Point {get;set;}
}

struct Circle
{
    public Point Point{get;set;}
    public decimal R{get;set;}
    string Name {get;set;}
}

class Program
{
    static void Main(string[] args)
    {
        Point p = new Point();//变量p在栈上
        Line line = new Line();//变量line在托管堆上
        line.Point = new Point();//字段Point在托管堆上

        Circle circle = new Circle();//变量circle在栈上
        circle.R = 100;//circle的字段R在栈上
        circle.Name = "circle1";//字段Name在托管堆上。
    }
}