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 类,使之能更好地处理同类型元素。
装箱是将值类型实例转换为引用类型实例的行为。引用类型可以是 object 类或接口(本章后面将介绍接口) [1] 。本例中,我们将 int 类型装箱成一个 object 对象:
拆箱操作刚好相反,它把 object 类型转换成原始的值类型:
拆箱需要显式类型转换。运行时将检查提供的值类型和真正的对象类型是否匹配,并在检查出错的时候抛出 InvalidCastException 。例如,下面的例子将抛出异常,因为 long 类型和 int 类型并不匹配:
下面的语句是正确的:
以下的语句也是正确的:
在上一个例子中, (double) 是拆箱操作而 (int) 是数值转换操作。
装箱转换对系统提供一致性的数据类型至关重要。但这个体系并不是完美的,3.9节会介绍数组和泛型的变量只能支持引用转换,不能支持装箱转换:
装箱是把值类型的实例复制到新对象中,而拆箱是把对象的内容复制回值类型的实例中。下面的示例修改了 i 的值,但并不会改变它先前装箱时复制的值:
C#程序在静态(编译时)和运行时(CLR)都会执行类型检查。
静态类型检查使编译器能够在程序没有运行的情况下检查程序的正确性。例如,编译器会强制进行静态类型检查,因而以下代码会出错:
在使用引用类型转换或者拆箱操作进行向下类型转换时,CLR会执行运行时类型检查,例如:
运行时可以进行类型检查,因为堆上的每一个对象都在内部存储了类型标识。这个标识可以通过调用 object 类的 GetType 方法得到。
C#中的所有类型在运行时都会维护 System.Type 类的实例。以下两个基本方法可以获得 System.Type 对象:
· 在类型实例上调用 GetType 方法。
· 在类型名称上使用 typeof 运算符。
GetType 在运行时计算而 typeof 在编译时静态计算(如果使用泛型类型参数,那么它将由即时编译器解析)。
System.Type 拥有诸多属性,例如,类型的名称、程序集、基类型等属性:
System.Type 同时还是运行时反射模型的访问入口,我们将在第18章中介绍该内容。
ToString 方法返回类型实例的默认文本描述。所有内置类型都重写了该方法。下面是对 int 类使用 ToString 方法的示例:
可以用下面的方式在自定义的类中重写 ToString 方法:
如果不重写 ToString 方法,那么它会返回类型的名称。
当直接在值类型对象上调用 ToString 这样的 object 成员时,若该成员是重写的,则不会发生装箱。只有进行类型转换时才会执行装箱操作:
以下列出了 object 的所有成员:
我们将在6.13节中介绍 Equals 、 ReferenceEquals 和 GetHashCode 方法。