C# 10核心技术指南
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 object类型

object类型(System.Object)是所有类型的最终基类。任何类型都可以向上转换为object类型。

为了说明这个类型的重要性,首先介绍通用栈。栈是一种遵循LIFO (Last-In First-Out,后进先出)的数据结构。栈有两种操作:将对象压入(Push)栈,以及将对象从栈中弹出(Pop)。以下是一个可以容纳10个对象的栈的简单实现:

由于Stack类操作的对象是object,所以可以实现Push或Pop任意类型的实例的操作。

object是引用类型,承载了类的优点。尽管如此,int等值类型也可以和object类型相互转换并加入栈中。C#这种特性称为类型一致化,以下是一个例子:

当值类型和object类型相互转换时,公共语言运行时(CLR)必须进行一些特定的工作来对接值类型和引用类型在语义上的差异。这个过程称为装箱(boxing)和拆箱(unboxing)。

在3.9节中,我们会介绍如何改进Stack类,使之能更好地处理同类型元素。

3.3.1 装箱和拆箱

装箱是将值类型实例转换为引用类型实例的行为。引用类型可以是object类或接口(本章后面将介绍接口)[1]。本例中,我们将int类型装箱成一个object对象:

拆箱操作刚好相反,它把object类型转换成原始的值类型:

拆箱需要显式类型转换。运行时将检查提供的值类型和真正的对象类型是否匹配,并在检查出错的时候抛出InvalidCastException。例如,下面的例子将抛出异常,因为long类型和int类型并不匹配:

下面的语句是正确的:

以下的语句也是正确的:

在上一个例子中,(double)是拆箱操作而(int)是数值转换操作。

装箱转换对系统提供一致性的数据类型至关重要。但这个体系并不是完美的,3.9节会介绍数组和泛型的变量只能支持引用转换,不能支持装箱转换:

装箱和拆箱中的复制语义

装箱是把值类型的实例复制到新对象中,而拆箱是把对象的内容复制回值类型的实例中。下面的示例修改了i的值,但并不会改变它先前装箱时复制的值:

3.3.2 静态和运行时类型检查

C#程序在静态(编译时)和运行时(CLR)都会执行类型检查。

静态类型检查使编译器能够在程序没有运行的情况下检查程序的正确性。例如,编译器会强制进行静态类型检查,因而以下代码会出错:

在使用引用类型转换或者拆箱操作进行向下类型转换时,CLR会执行运行时类型检查,例如:

运行时可以进行类型检查,因为堆上的每一个对象都在内部存储了类型标识。这个标识可以通过调用object类的GetType方法得到。

3.3.3 GetType方法和typeof运算符

C#中的所有类型在运行时都会维护System.Type类的实例。以下两个基本方法可以获得System.Type对象:

· 在类型实例上调用GetType方法。

· 在类型名称上使用typeof运算符。

GetType在运行时计算而typeof在编译时静态计算(如果使用泛型类型参数,那么它将由即时编译器解析)。

System.Type拥有诸多属性,例如,类型的名称、程序集、基类型等属性:

System.Type同时还是运行时反射模型的访问入口,我们将在第18章中介绍该内容。

3.3.4 ToString方法

ToString方法返回类型实例的默认文本描述。所有内置类型都重写了该方法。下面是对int类使用ToString方法的示例:

可以用下面的方式在自定义的类中重写ToString方法:

如果不重写ToString方法,那么它会返回类型的名称。

当直接在值类型对象上调用ToString这样的object成员时,若该成员是重写的,则不会发生装箱。只有进行类型转换时才会执行装箱操作:

3.3.5 object的成员列表

以下列出了object的所有成员:

我们将在6.13节中介绍Equals、ReferenceEquals和GetHashCode方法。