object 类型
object
类型(System.Object
)是所有类型的终极父类。任何类型都可以向上转换为 object
类型。
public class Stack
{
int position;
object[] data = new object[10];
public void Push(object obj)
{
data[position++] = obj;
}
public object Pop()
{
return data[--position];
}
}
由于 Stack
类的操作对象是 object
,所以实现 Push
和 Pop
任意类型的实例的操作。
Stack stack = new Stack();
stack.Push ("sausage"); // Upcast
string s = (string) stack.Pop(); // Downcast, so explicit cast is needed
Console.WriteLine (s); // sausage
object
是引用类型,承载了类的优点。但 int
等值类型也可以和 object
类型相互转换并加入栈中。csharp 称这种特性为类型一致化。
stack.Push (3);
int three = (int) stack.Pop();
当值类型和 object
类型相互转换时,公共语言运行时(CLR)必须进行一些特定的工作来对接值类型和引用类型在语义上的差异,这个过程称为装箱(boxing)和拆箱(unboxing)。
1 装箱和拆箱 Boxing and Unboxing
-
装箱
- 装箱是将值类型实例转换为引用类型实例。
- 引用类型可以是
object
类或接口。
int x = 9; object obj = x; // Box the int
🎃 一个不可空的值类型装箱之后,其结果是得到一个类型的对象的引用,这里的类型是原式类型装箱后的一种形态:
int x = 5; object o = x;
这里的
o
就是到“装箱的后int
”的一个对象的引用。装箱后的int
和int
在 csharp 中其实是看不出来的,即如果调用o.GetType()
其结果和typeof(int)
是一样的。 -
拆箱
-
拆箱与装箱相反,它把
object
类型转换为原始的值类型。int y = (int)obj; // Unbox the int
-
拆箱需要显式类型转换。运行时将检查提供的值类型和真实的对象类型是否匹配,并在检查出错时抛出
InvalidCastException
:object obj = 9; // 9 is inferred to be of type int long x = (long) obj; // InvalidCastException
以下语句是正确的:
object obj = 9; int x = (int)obj;
object obj = 3.5; // 3.5 is inferred to be of type double int x = (int)(double)obj; // x is now 3 // (double) 是拆箱操作而 (int) 是数值转换操作
-
装箱转换对系统提供一致性的数据类型至关重要,但该体系不够完美,数组和泛型只支持引用转换,不支持装箱转换。
object[] a1 = new string[3]; // Legal object[] a2 = new int[3]; //Error
-
-
装箱和拆箱的复制
- 装箱是把值类型的实例复制到一个新对象中。
- 拆箱是把对象的内容复制到一个新的值类型的实例。
int i = 3; object boxed = i; i = 5; Console.WriteLine(boxed); // 3 object obj = 3; int unboxed = (int)obj; obj = 5; Console.WriteLine(unboxed); // 3
2 静态和运行时类型检查
csharp 程序在静态(编译时)和运行时(CLR)都会执行类型检查。
-
静态类型检查使编译器在程序没有运行的情况下检查程序的正确性。例如,因为编译器会强制进行静态类型检查因而以下代码会出错:
int x = "5";
-
运行时的类型检查有 CLR 执行,在使用引用类型转换或者拆箱操作进行向下类型转换时发生:
object y = "5"; int x = (int)y; // Runtime error, downcast failed
运行时可以进行类型检查是因为堆(Heap)上的每一个对象都在内部存储了类型标识,这个标识可以通过
object
类的GetType
方法得到。
3 GetType 方法和 typeof 操作符
csharp 所有类型在运行时都以 System.Type
类的实例表示,两种基本方法可以获得 System.Type
对象:
- 在类型实例上调用
GetType
方法。- 在类型名称上使用
typeof
操作符。
GetType
是在运行时被计算出的。typeof
是在编译时静态计算出的(如果是使用泛型类型参数,那么它将由即时编译器(JIT)解析)。
☀️System.Type
拥有诸多属性,例如类型的名称、程序集、基类型等属性:
class Point {public int x, y;}
class Program
{
static void Main(string[] args)
{
Point p = new Point();
Console.WriteLine(p.GetType().Name); // Point
Console.WriteLine(typeof(Point).Name); // Point
Console.WriteLine(p.GetType() == typeof(Point)); // true
Console.WriteLine(p.x.GetType().Name); // Int32
Console.WriteLine(p.y.GetType().FullName); // System.Int32
}
}
🌴 当在值类型上调用 GetType()
方法时,这个值类型首先必须装箱。对可空值类型来说,它要么引起 NullReferenceException
异常,要么返回底层的非可空值类型。
int? noValue = null;
Console.WriteLine(noValue.GetType()); // NullReferenceException
int? hasValue = new int?(5);
Console.WriteLine(hasValue.GetType()); // System.Int32
4 ToString 方法
ToString
方法返回类型实例的默认文本描述。所有内置类型都重写了方法。
int x = 1;
string s = x.ToString(); // s is "1"
可以使用下面的方法在自定义的类中重写 ToString
方法:
public class Panda
{
public string name;
public override string ToString() => name;
}
Panda p = new Panda {name = "Petey"};
Console.WriteLine(p.ToString()); // Petey
如果不重写 ToString
方法,将返回类型的名称。
🍂 当直接在值类型对象上调用 ToString
这样的 object
成员时,若改成员是重写的则不会发生装箱。只有进行类型转换时才会执行装箱操作:
int x = 1;
string s1 = x.ToString(); // Calling on nonboxed value
object box = x;
string s2 = box.ToString(); // Calling on boxed value