C#/.NET值类型
值类型 (Value Type) 包括两个成员:结构体和枚举类型。
通常来说,值类型就是字面意义上的那种值,例如整数 int,小数 float/double,布尔值等。
而实际上,整数、小数,布尔值等全部都是结构体。
值类型的默认值一般为 0,例如整数和小数的默认值都是 0,枚举类型的默认值也为 0, char 的默认值为 ‘\0’。和引用类型相比,值类型的内存分配简单得多。
基元类型
之前讲过,C# 和其他 .NET 语言都是运行在通用类型系统(CTS)上的,而 CTS 提供 一些“基本的”类型一基元类型(Primitive Type)。
各个 .NET 语言分别使用不同的关键字, 但最终它们都会被映射到同一个 IL 类型。这样的类型就叫做基元类型,它们由 CTS 定义,由编译器与 BCL 直接支持,属于 BCL 而非任何某个语言。
基元类型包括了几乎所有的值类型(除了用户定义的结构体和枚举)以及字符串,object 和 dynamic。
Primitive 有原始的意思,可以将基元类型理解为基本的、原始的类型,少了它们就什么都做不了。
有了基元类型,各个 .NET 语言的互操作性就可以实现了,例如,通过 ildasm 工具,我们可以查看到 int i=1 对应的 IL 代码为(这里省略了赋值的那一句 IL代码):
.locals init ([0] int32 i)
这说明了在 IL 中 int 对应的基元类型为 Int32。当然,在 C# 中也可以直接写 Int32 i=1,不过,这样并不会给你带来任何好处。
而对于VB.NET,int 的关键字为 Integer,如果你在 VB.NET 中声明了一个Integer,你也可以通过 ildasm 发现,它对应的类型仍然为 Int32。
值类型的内存分配
值类型的内存分配分为以下几种情况:
- 值类型作为局部变量。
- 值类型作为引用类型的成员。
- 值类型中包含引用类型。
1) 值类型作为局部变量
普通的值类型总是分配在栈上。例如以最简单的 int 为例,inti=1 意味着我们在栈上开辟了一块空间存储这个值类型。
注意,int 实际上是一个结构体,它有 2 个值类型成员(最大值,最小值),它们是常量,所以是静态的(const=static readonly)。
静态的成员和 int 的方法均存储在加载堆中。
值类型也没有同步块索引和类型对象指针。所以,新建一个 int,不会重新复制它的最大值和最小值,int 的开销永远是 4 个字节(就是它自己)。
即使机器是 64 位机,int 的大小永远是 32 位,因为 int 实质上是 Int32。Int64 这个基元类型在 C# 中对应 long。
对于局部变量的复制来说,情况非常简单。我们知道,值类型复制时,将只复制值的副本。所以更改原值对复制的新值不会有影响。
var i = 1; var j = i; i = 2 ; //输出1 Console.WriteLine(j);
当执行代码var j = i
时,将会在栈上新建一个名为 j 的变量,然后将 i 的值复制给 j,它和 i 没有任何关系。值类型也不可能有浅复制。
2) 值类型作为引用类型的成员
如果值类型为引用类型的成员,则遵从引用类型的内存分配和复制方式。例如:
public class AClass { public int a; public string b; }
在创建一个该类的实例时,遵从引用类型的内存分配方式。
下面的代码会实例化一个 AClass 对象:
var a = new AClass(); a. a = 1; a.b = "hey";
执行完上面的代码之后,内存的分配如下图所示。
发表评论