C#中派生类对象实例化为基类型与派生类型有何区别?
核心差异点
编译时类型vs运行时类型
当把派生类实例化为基类型(比如Shape circle = new Circle())时,编译时变量的类型是基类Shape,但运行时实际创建的对象是派生类Circle;而直接实例化为派生类型(Rectangle rectangle = new Rectangle())时,编译时和运行时的类型完全一致,都是Rectangle。可访问成员的限制
变量能调用的成员由编译时类型决定:- 基类型变量只能访问基类中定义的成员,哪怕派生类有自己扩展的成员,也无法直接调用(必须先显式转换为派生类型才能访问)。
- 派生类型变量可以直接访问自身扩展的成员,以及从基类继承来的所有成员。
多态行为的表现
如果基类中有虚方法,派生类重写了该方法:- 基类型变量调用该方法时,会执行派生类的重写实现(这就是C#的多态特性)。
- 派生类型变量调用该方法时,同样会执行派生类的重写实现。
但如果派生类用new关键字隐藏了基类的成员,基类型变量调用的是基类的版本,派生类型变量调用的是自己的版本。
类型转换的规则
- 派生类型转基类型是隐式的(比如
Shape s = rectangle;),因为派生类天然"是一种"基类(is-a关系)。 - 基类型变量转派生类型必须显式转换(比如
Circle c = (Circle)circle;),转换前最好用is或as判断类型是否匹配,避免抛出转换异常。
- 派生类型转基类型是隐式的(比如
针对示例代码的具体解析
先看示例代码:
var shape = new Shape(); // shape = {Shape} Shape circle = new Circle(); // circle [Shape] = {Circle} Rectangle rectangle = new Rectangle(); // rectangle = {rectangle} class Shape {} class Circle : Shape {} class Rectangle : Shape {}
var shape = new Shape();var让编译器自动推断变量类型为Shape,编译时和运行时对象都是Shape,所以调试器显示{Shape},变量只能访问Shape类的成员(示例中Shape是空类,没有自定义成员)。Shape circle = new Circle();
变量circle的编译时类型是Shape,所以调试器标注了[Shape];但实际创建的对象是Circle,所以调试器显示{Circle}。此时如果Circle类有自己的成员(比如public void DrawCircle()),circle变量无法直接调用这个方法,必须先转成Circle类型:((Circle)circle).DrawCircle();。Rectangle rectangle = new Rectangle();
变量的编译时和运行时类型都是Rectangle,调试器直接显示{Rectangle}。如果Rectangle有扩展成员,rectangle变量可以直接调用,同时也能访问Shape的成员。
内容的提问来源于stack exchange,提问作者akTed

