类型基础
所有类型都从Syetem.Object派生
运行时要求每个类型最终都从System.Object类型派生。换言之,以下两个类型的定义是完全一致的:
隐式派生自Object: public class Employee { public string Name { get; set; } } 显式派生自Object: public class Employee : Object { public string Name { get; set; } } |
由于所有类型最终都从System.Object派生,所以可以保证每个类型的每个对象都有一组最基本的方法。具体来说,System.Object类提供了如下的公共实例方法:
公共方法名称 |
说明 |
Equals |
确定两个 Object 实例是否相等。 |
GetHashCode |
用作特定类型的哈希函数。 |
ToString |
返回表示当前 Object 的 String。 |
GetType |
获取当前实例的 Type。 |
此外,从System.Object派生的类型可以访问如下所示的受保护方法:
受保护方法名称 |
说明 |
MemberwiseClone |
创建当前 Object 的浅表副本。 |
Finalize |
允许 Object 在“垃圾回收”回收 Object 之前尝试释放资源并执行其他清理操作。 |
CLR要求所有的对象都用new关键字进行创建:
Employee employee = new Employee(); |
以下过程是new操作符所作的事:
1. 计算类型及其所有基类型中定义的所有实例字段所需要的字节数。
2. 从托管堆中分配指定类型所需的字节数,从而分配内存空间。
3. 初始化对象的“类型对象指针”(type object pointer)与“同步块索引成员”(sync block index)。
4. 调用实例的构造器,向其传入在对new的调用中指定的任何实参。
new执行了这些操作之后,会返回指向新建对象的一个引用。在前面的示例中,这个引用会保存到变量employee中,后者具有Employee类型。
因为Employee类型从Syetem.Object派生,所以Employee类型实例就具有了Syetem.Object定义的可访问的实例方法。以ToString()方法为例,它在默认情况下输入完整类型名称:
代码: Employee employee = new Employee(); employee.Name = "Sunny D.D"; Console.WriteLine(employee.ToString()); 输出: |
当然我们也可以重写ToString()方法,以获得想要的结果:
public class Employee : Object { public string Name { get; set; } public override string ToString() { return Name; } } |
类型转换
CLR最重要的特性之一就是类型安全性。在运行时,CLR总是可以调用GetType()方法得到一个对象的确切类型是什么。由于GetType()是非虚方法,所以一个类型不可能伪装成另一个类型。例如,Employee类型不能重写GetType()方法,从而返回一个String类型。即便你可以使用new关键字覆盖此方法,但CLR是在基类型Object上调用此方法的,所以仍然可以得到当前对象的确切类型:
public class Employee : Object { public string Name { get; set; } public override string ToString() { return Name; } public new Type GetType() { return typeof(String); } } 调用: Employee employee = new Employee(); Console.WriteLine(employee.GetType()); object objEmployee = employee; Console.WriteLine(objEmployee.GetType()); 结果: |
在实际开发过程中,我们常需要将对象从一种类型转换为其他类型。CLR允许将一个对象转换为它的实际类型,或者它的任何基类型。
在C#中,不需要任何特殊语法即可将一个对象转换为它的任何基类型,因为向基类型的转换被认为是一种安全的转换。然而,将对象转换为它的某个派生类型时,C#要求必须进行显式转换,因为这可能在运行时失败。以下代码演示了基类型与派生类之间的相互转换:
object o = new Employee(); Employee e = (Employee)o; |
这个例子展示了你需要做什么,才能让编译器顺利的编译代码。接着,我将演示一个虽然可以通过编译,但会在运行时抛出一个InvalidCastException(因无效类型转换或显式转换引发的异常):
public class Manager : Employee { } 调用: Employee e = null; object m = new Manager(); e = (Employee)m; object d = new DateTime(1984, 10, 17); e = (Employee)d; 结果: |
可以看到,在上述代码实际执行过程中,发生的第一次转换e = (Employee)m;成功了,这是由于m的类型Manager是从Employee派生的,所以CLR成功执行类型转换,继续往下进行。而在第二次转换e = (Employee)d;时,CLR抛出了一个异常,这是因为d的类型DateTime既不是一个Employee