使用基类的引用
派生类的实例由基类的实例和派生类新增的成员组成。派生类的引用指向整个类对象,包括
基类部分。
如果有一个派生类对象的引用,就可以获取该对象基类部分的引用(使用类型转换运算符把
该引用转换为基类类型)。类型转换运算符放置在对象引用的前面,由圆括号括起的要被转换成
的类名组成。类型转换将在第17章阐述。将派生类对象强制转换为基类对象的作用是产生的变
量只能访问基类的成员(在被覆写方法中除外,稍后会讨论)。
接下来的几节将阐述使用对象的基类部分的引用来访问对象。我们从观察下面两行代码开
始,它们声明了对象的引用。图8-6阐明了代码,并展示了不同变量所看到的对象部分。
- 第一行声明并初始化了变量derived,它包含一个MyDerivedClass类型对象的引用。
- 第二行声明了一个基类类型MyBaseClass的变量,并把derived中的引用转换为该类型,
给出对象的基类部分的引用。- 基类部分的引用被存储在变量mybc中,在赋值运算符的左边。
- 基类部分的引用“看不到"派生类对象的其余部分,因为它通过基类类型的引用“看”
这个对象。
MyDerivedClass derived=new MyDerivedClass(); //创建一个对象
MyBaseClass mybc=(MyBaseClass) derived; //转换引用
下面的代码展示了两个类的声明和使用。图8-7阐明了内存中的对象和引用。
Main创建了一个MyDerivedClass类型的对象,并把它的引用存储到变量derived中。Main
还创建了一个MyBaseClass类型的变量,并用它存储对象基类部分的引用。当对每个引用调用
print方法时,调用的是该引用所能看到的方法的实现,并产生不同的输出字符串。
class MyBaseClass
{public void Print(){Console.WriteLine("This is the base class.");}
}class MyDerivedClass:MyBaseClass
{public int var1;new public void Print(){Console.WriteLine("This is the derived class.");}
}class Program
{static void Main(){MyDerivedClass derived=new MyDerivedClass();MyBaseClass mybc=(MyBaseClass)derived; //转换为基类derived.Print(); //从派生类部分调用Printmybc.Print(); //从基类部分调用Print//mybc.var1=5; //错误:基类引用无法访问派生类成员}
}
虚方法和覆写方法
在上一节我们看到,当使用基类引用访问派生类对象时,得到的是基类的成员。虚方法可以
使基类的引用访问“升至“派生类内。
可以使用基类引用调用派生类的方法,只需满足下面的条件。
- 派生类的方法和基类的方法有相同的签名和返回类型。
- 基类的方法使用virtual标注。
- 派生类的方法使用override标注。
例如,下面的代码展示了基类方法和派生类方法的virtual及override修饰符。
class MyBaseClass //基类
{virtual public void Print()....
}class MyDerivedClass:MyBaseClass //派生类
{override void Print()}
图8-8阐明了这组virtual和override方法。注意和上一种情况(用new隐藏基类成员)相
比在行为上的区别。
- 当使用基类引用(mybc)调用Print方法时,方法调用被传递到派生类并执行,因为:
- 基类的方法被标记为virtual;
- 在派生类中有匹配的override方法。
- 图8-8阐明了这一点,显示了一个从virtual Print方法后面开始,并指向overridePrint
方法的箭头。
下面的代码和上一节中的相同,但这一次,方法上标注了virtual和override。产生的结果
和前一个示例有很大不同。在这个版本中,对基类方法的调用实际调用了子类中的方法。
class MyBaseClass
{virtual public void Print(){Console.WiteLine("This is the base class.");}
}class MyDerivedClass:MyBaseClass
{public override void Print(){Console.WriteLine("This is the derived class.");}
}class Program
{static void Main(){MyDerivedClass derived=new MyDerivedClass();MyBaseClass mybc=(MyBaseClass)derived; //强制转换成基类derived.Print();mybc.Print();}
}
其他关于virtual和override修饰符的重要信息如下。
- 覆写和被覆写的方法必须有相同的可访问性。例如,这种情况是不可以的:被覆写的方
法是private的,而覆写方法是public的。 - 不能覆写static方法或非虚方法。
- 方法、属性和索引器(前一章阐述过),以及另一种成员类型一一事件(将在后面阐述),
都可以被声明为virtual和override。
覆写标记为override的方法
覆写方法可以在继承的任何层次出现。
- 当使用对象基类部分的引用调用一个被覆写的方法时,方法的调用被沿派生层次上溯执
行,一直到标记为override的方法的最高派生(most-derived)版本。 - 如果在更高的派生级别有该方法的其他声明,但没有被标记为override,那么它们不会
被调用。
例如,下面的代码展示了3个类,它们形成了一个继承层次:MyBaseClass、MyDerivedClass
和SecondDerived。所有这3个类都包含名为Print的方法,并带有相同的签名。在MyBaseClass
中,Print被标记为virtual。在MyDerivedClass中,它被标记为override。在类SecondDerived
中,可以使用override或new声明方法Print。让我们看一看在每种情况下将发生什么。
class MyBaseClass //基类
{virtual public void Print(){COnsole.WriteLine("This is the base class.");}
}class MyDerivedClass:MyBaseClass //派生类
{override void Print(){Console.WriteLine("This is the derived class.");}}class SecondDerived:MyDerivedClass //最高派生类
{...//在后面给出
}
情况1:使用override声明Print
如果把SecondDerived的Print方法声明为override,那么它会覆写方法的两个低派生级别的
版本,如图8-9所示。如果一个基类的引用被用于调用Print,它会向上传递,一直到类secondDerived
中的实现。
下面的代码实现了这种情况。注意方法Main的最后两行代码。
- 两条语句中的第一条使用最高派生类SecondDerived的引用调用Print方法。这不是通过
基类部分的引用的调用,所以它将会调用SecondDerived中实现的方法。 - 而第二条语句使用基类MyBaseClass的引用调用Print方法。
class SecondDerived:MyDerivedClass
{override public void Print(){Console.WriteLine("This is the second derived class.");}
}class Program
{static void Main(){SecondDerived derived=new SecondDerived();//使用SecondDerivedMyBaseClass mybc=(MybaseClass)derived; //使用MyBaseClassderived.Print();mybc.Print();}
}
结果是:无论Print是通过派生类调用还是通过基类调用的,都会调用最高派生类中的方法。
当通过基类调用时,调用沿着继承层次向上传递。这段代码产生以下输出:
This is the second derived class.
This is the second derived class.
2.情况2:使用new声明Print
相反,如果将SecondDerived中的Print方法声明为new,则结果如图8-10所示。Main和上
一种情况相同。
class SecondDerived:MyDerivedClass
{new public void Print(){Console.WriteLine("This is the second derived class.");}
}class Program
{static void Main(){SecondDerived derived=new SecondDerived();//使用SecondDerivedMyBaseClass mybc=(MyBaseClass)derived; //使用MyBaseClassderived.Print();mybc.Print();}
}
结果是:当通过SecondDerived的引用调用方法Print时,SecondDenved中的方法被执行,
正如所期待的那样。然而,当通过MyBaseClass的引用调用Print方法时,方法调用只向上传递
了一级,到达类MyDerived,在那里它被执行。两种情况的唯一不同是SecondDerived中的方法
使用修饰符override还是修饰符new声明。
这段代码产生以下输出:
This is the second derived class.
This is the derived class.
覆盖其他成员类型
在之前的几节中,我们已经学习了如何在方法上使用virtual/override。在属性、事件以及
索引器上的用法也是一样的。例如,下面的代码演示了名为MyProperty的只读属性,其中使用
了virtual/override。
class MyBaseClass
{private int _myInt=5;virtual public int MyProperty{get{return _myInt;}}
}class MyDerivedClass:MyBaseClass
{private int _myInt=10;override public int MyProperty {get{return _myInt;}}
}class Program
{static void Main(){MyDerivedClass derived=new MyDerivedClass();MyBaseClass mybc=(MyBaseClass)derived;Console.WriteLine(derived.MyProperty);Console.WriteLine(mybc.MyProperty);}
}