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]; } }
这是一个后进先出的这么一个栈,因为是object类型,所以你可以Push和Pop任意的类型到这个栈里
Stack stack = new Stack(); stack.Push ("sausage"); string s = (string) stack.Pop(); // 向下转换,显式的转换一下 Console.WriteLine (s); // sausage
object是引用类型,但值类型可以转换为object,反之亦然。(类型统一)
stack.Push (3); int three = (int) stack.Pop();
在值类型和object之间转换的时候,CLR必须执行一些特殊的工作,以弥补值类型和引用类型之间语义上的差异,这个过程就叫做装箱和拆箱。
装箱(boxing)
装箱就是把值类型的实例转换为引用类型的实例的动作,目标引用类型可以是object,也可以是某个接口
int x = 9; object obj = x; // 把int值装箱
拆箱(unboxing)
拆箱正好和装箱相反,把对象转换为原来的值类型
int y = (int)obj; // 还原int值
拆箱需要显式的转换
拆箱过程中,运行时会检查这个值类型和object对象的真实类型是否匹配,如果不匹配就抛出InvalidCastException
object obj = 9; // 9 在这里是int类型 long x = (long) obj; // InvalidCastException
下面的转换就是可以的,int类型可以隐式的转换为long类型,但是像上面的直接拆箱就不可以:
object obj = 9; long x = (int) obj;
装箱对于类型统一是非常重要的,但是系统设计还是不够完美,比如数组和泛型只支持引用转换,不支持装箱
object[] a1 = new string[3]; // 可以的 object[] a2 = new int[3]; // 会报错
装箱拆箱的复制
- 装箱会把值类型的实例复制到一个新的对象
- 拆箱会把这个对象的内容再复制给一个值类型的实例
看个例子:
int i = 3; object boxed = i; i = 5; Console.WriteLine (boxed); // 3
静态和运行时类型检查
C#的程序既会做静态的类型检查(编译时),也会做运行时的类型检查(CLR)
静态检查:就是不运行程序的情况下,让编译器保证你的程序的正确性,比如 int x = "5"; 这么写肯定是不行的
运行时的类型检查由CLR执行,发生在向下的引用转换或拆箱的时候。
object y = "5"; int z = (int) y; // 运行时报错,向下转换失败
运行时检查之所以可行是因为每个在heap上的对象内部都存储了一个类型token。这个token可以通过调用object的GetType()方法来获取。
GetType方法与typeof操作符
所有C#的类型在运行时都是以System.Type的实例来展现的
有两种方式可以获得System.Type对象:一是在实例上调用GetType()方法;第二个是在类型名上使用typeof操作符。
GetType是在运行时被算出的,typeof是在编译时被算出的(静态)(当涉及到泛型类型参数时,它是由JIT编译器来解析的)
System.Type
System.Type的属性有:类型名称、Assembly、基类等等。直接看例子:
using System; public class Point { public int X, Y; } class Test { static void Main() { 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 } }
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); // Petey
当你调用一个被重写的object成员的时候,例如在值类型上直接调用ToString()方法,这时候就不会发生装箱操作,但是如果你进行了转换,那么装箱操作就会发生
int x = 1; string s1 = x.ToString(); // 这里就没有发生装箱 object box = x; string s2 = box.ToString(); // 调用的就是装箱后的值