首页 > 编程笔记 > C#笔记 阅读:7

C#值类型和引用类型的区别(非常详细)

C# 中的类型可以分为 4 类,分别是值类型、引用类型、泛型参数和指针类型。本节将介绍值类型和引用类型。

值类型包含大多数的内置类型(具体包括所有数值类型、char 类型和 bool 类型)以及自定义的 struct 类型和 enum 类型。

引用类型包含所有的类、数组、委托和接口类型,其中包括了预定义的 string 类型。

值类型和引用类型最根本的不同在于它们在内存中的处理方式。

C#值类型

值类型的变量或常量的内容仅仅是一个值。例如,内置的值类型 int 的内容是 32 位的数据。

可以通过 struct 关键字定义自定义值类型:
public struct Point { public int X; public int Y; }
或采用更简短的形式:
public struct Point { public int X, Y; }

值类型实例的赋值总是会进行实例复制,例如:
Point p1 = new Point();
p1.X = 7;

Point p2 = p1; // Assignment causes copy

Console.WriteLine(p1.X); // 7
Console.WriteLine(p2.X); // 7

p1.X = 9; // Change p1.X

Console.WriteLine(p1.X); // 9
Console.WriteLine(p2.X); // 7
下图展示了 p1 和 p2 拥有不同的存储空间。


图 1 赋值操作复制了值类型的实例

C#引用类型

引用类型比值类型复杂,它由对象和对象引用两部分组成。引用类型变量或常量中的内容是一个含值对象的引用。

例如重新书写前面例子中的 Point 类型,令其成为一个类而非 struct:
public class Point { public int X, Y; }
给引用类型变量赋值只会复制引用,而不是对象实例。这允许不同变量指向同一个对象,而值类型通常不会出现这种情况。如果 Point 是一个类,那么若重复之前的示例,则对 p1 的操作就会影响到 p2 了:
Point p1 = new Point();
p1.X = 7;

Point p2 = p1; // Copies p1 reference

Console.WriteLine(p1.X); // 7
Console.WriteLine(p2.X); // 7

p1.X = 9; // Change p1.X

Console.WriteLine(p1.X); // 9
Console.WriteLine(p2.X); // 9
下图展示了 p1 和 p2 是指向同一对象的两个不同引用。


图 2 赋值操作复制了引用

C# null

引用可以用字面量 null 来赋值,表示它并不指向任何对象:
Point p = null;

Console.WriteLine(p == null);  // True

// The following line generates a runtime error
// (a NullReferenceException is thrown):
Console.WriteLine(p.X);

class Point {...}

相对地,值类型通常不能为 null:
Point p = null;  // Compile-time error
int x = null;  // Compile-time error

struct Point {...}

C#存储开销

值类型实例占用的内存大小就是存储其中的字段所需的内存。例如,Point 需要占用 8 字节的内存:
struct Point
{
    int x;  // 4 bytes
    int y;  // 4 bytes
}
从技术上说,CLR 用整数倍字段的大小(最大到8字节)来分配内存地址。因此,下面定义的对象实际上会占用 16 字节的内存(第一个字段的 7 个字节被“浪费了”):
struct A { byte b; long l; }
这种行为可以通过指定 StructLayout 特性来重写。

引用类型需要为引用和对象单独分配存储空间。对象除占用了和字段一样的字节数外,还需要额外的管理空间开销。管理开销的精确值本质上属于 .NET 运行时实现的细节,但最少也需要 8 字节来存储该对象类型的键、一些诸如线程锁的状态,以及是否可以被垃圾回收器固定等临时信息。根据 .NET 运行时工作的平台类型(32 位或 64 位平台),每一个对象的引用都需要额外的 4 字节或 8 字节的存储空间。

相关文章