C# 7.0 核心技术指南 (19):C#语言基础 2.3.4

阅读数:10 2019 年 11 月 30 日 23:37

C# 7.0核心技术指南(19):C#语言基础 2.3.4

(值类型与引用类型)

内容简介
本书前三章将集中介绍 C#语言。首先介绍基本的语法、类型和变量。而后会介绍一些高级的特性,如不安全代码以及预处理指令。如果你是 C#语言的初学者,请循序渐进地阅读这些章节。
其余各章则涵盖了.NET Framework 的核心功能,包括 LINQ、XML、集合、并发、I/O 和网络、内存管理、反射、动态编程、特性、安全、应用程序域和原生互操作性等主题。第 6 章和第 7 章是后续主题的基础,除这两章之外,其余各章可以按照需要以任何顺序阅读。LINQ 相关的三个章节好按顺序阅读。其中的一些章节需要一些并发相关的知识,这些知识将在第 14 章中介绍。

所有的 C#类型可以分为以下几类:

  • 值类型
  • 引用类型
  • 泛型参数
  • 指针类型

本节将介绍值类型和引用类型。泛型参数将在 3.9 节介绍,指针类型将在 4.15 节中介绍。

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

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

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

2.3.4.1 值类型

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

可以通过 struct 关键字定义自定义值类型(参见图 2-1):

C# 7.0核心技术指南(19):C#语言基础 2.3.4

图 2-1:内存中的值类型实例
复制代码
public struct Point { public int X; public int Y; }

或采用更简短的形式:

复制代码
public struct Point { public int X, Y; }

值类型实例的赋值总是会进行实例复制。例如:

复制代码
static void Main()
{
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
}

图 2-2 中展示了 p1 和 p2 拥有不同的存储空间。

C# 7.0核心技术指南(19):C#语言基础 2.3.4

图 2-2:赋值操作复制了值类型的实例

2.3.4.2 引用类型

引用类型比值类型复杂,它由两部分组成:对象和对象引用。引用类型变量或常量中的内容是一个含值对象的引用。以下示例将前面例子中的 Point 类型重新书写,令其成为一个类而非 struct(请参见图 2-3):

C# 7.0核心技术指南(19):C#语言基础 2.3.4

图 2-3:内存中的引用类型实例
复制代码
public class Point { public int X, Y; }

给引用类型变量赋值只会复制引用,而不是对象实例。这允许不同变量指向同一个对象,而值类型通常不会出现这种情况。如果 Point 是一个类,那么若重复之前的示例,则对 p1 的操作就会影响到 p2 了:

复制代码
static void Main()
{
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
}

图 2-4 展示了 p1 和 p2 是指向同一对象的两个不同引用。

C# 7.0核心技术指南(19):C#语言基础 2.3.4

图 2-4:赋值操作复制了引用

2.3.4.3 Null

引用可以赋值为字面量 null,表示它并不指向任何对象:

复制代码
class Point {...}
...
Point p = null;
Console.WriteLine (p == null); // True
// The following line generates a runtime error
// (a NullReferenceException is thrown):
Console.WriteLine (p.X);

相对地,值类型通常不能有 null 的取值:

复制代码
struct Point {...}
...
Point p = null; // Compile-time error
int x = null; // Compile-time error

C#中也有一种代表值类型为 null 的结构,称为可空(nullable)类型(请参见 4.7 节)。

2.3.4.4 存储开销

值类型实例占用的内存大小就是存储其字段所需的内存。例如,Point 需要占用 8 字节的内存:

复制代码
struct Point
{
int x; // 4 bytes
int y; // 4 bytes
}

从技术上说,CLR 用整数倍字段的大小(最大到 8 字节)来分配内存地址。因此,下面的定义的对象实际上会占用 16 字节的内存(第一个字段的 7 个字节被“浪费了”):

复制代码
struct A { byte b; long l; }

这种行为可以通过指定 StructLayout 属性来重写(请参见 25.6 节)。

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

C# 7.0核心技术指南(19):C#语言基础 2.3.4

购书地址 https://item.jd.com/12681788.html?dist=jd

评论

发布