继承与多态之方法的重载、隐藏与重写辨析

        多态特性的根源是继承,虽然继承在概念上很容易理解,但还是给编程带来了一些容易混淆和引发混乱的东西,下面我们来讨论子类父类成员同名所引发的问题。

有过面向对象编程经验的人都知道,子类对象同时汇集了父类和子类定义的所有公共方法,但C#并未对子类和父类的方法名称进行过多限制,因此问题出现了:

        如果子类中某个方法与父类方法完全一样(即方法名和方法参数都一样),那么当通过子类对象访问此方法时,被访问的是子类还是父类所定义的方法?

再考虑复杂一些:

        假设父类变量引用一个子类对象,那么通过父类变量访问此方法,被访问的是子类还是父类所定义的方法?

我们从子类方法与父类方法之间的关系,来印证上述问题答案。

总的来说,子类方法与父类方法之间关系可以概括为以下三种:

扩充:父类中没有与子类同名的方法。

重载:子类有父类的同名方法,但参数类型或数目不一样。

完全相同:子类方法与父类方法从方法名称到参数列表都完全一样。

        对于第一种“扩充”关系,由于子类与父类方法不同名,所以也就不存在同名方法调用的问题,下面重点分析一下后两种情况。

0.1 继承关系下的方法重载

方法重载是面向对象对结构化编程特性的一个重要扩充。

构成“重载”的方法具有以下特点:

  1. 方法名相同
  2. 方法参数列表不同

        判断上述第2点的标准有三点,满足任一点均可认定方法参数列表不同:

  1. 方法的参数数目不同:比如一个方法有三个参数,另一个方法只有一个参数。
  2. 方法拥有相同数目的参数,但是参数类型不一样。
  3. 方法拥有相同数目的参数和参数类型,但参数类型出现的先后顺序不一样。

        需要注意的是,方法返回值类型不能作为方法重载的判断条件。

// 如以下所示,C#编辑器将报告错误。
public long Add(int x,int y) {......}
public int Add(int x,int y) {......}

构成重载的方法主要根据参数列表来决定调用哪一个。

以下代码在子类和父类中定义了一个重载的方法OverloadF:

class Parent {
    public void OverloadF(){
        ......
    }
}

class Child : Parent {
    public void OverloadF(int i){
        ......
    }
}

使用代码如下:
Child obj = new Child();
obj.OverloadF();    // 调用父类的重载方法
obj.OverloadF(100);    // 调用子类的重载方法

        可以看到,虽然重载的方法分布在不同的类中,但仍然可以将其看成是定义在同一个类中的,其使用方式与调用类的其他方法并无不同。

0.2 子类隐藏父类的方法

当子类与父类拥有完全一样的方法时,称子类“隐藏”了父类的同名方法。

class Parent
{
    public void HideF()
    {
        Console.WriteLine("Parent.HideF()");
    }
}
class Child : Parent
{
    public void HideF()
    {
        Console.WriteLine("Child.HideF()");
    }
}

请注意,现在子类和父类都拥有了一个完全相同的方法HideF,请看以下代码输出:

Child c = new Child();
c.HideF();    // 输出:Child.HideF()
修改一下代码再看其输出:
Parent p = new Child();
p.HideF();    // 输出:Parent.HideF()

由此可得出一个结论:

        当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。

面向对象的继承特性允许子类对象被当成父类对象使用,这就使问题复杂化了

Parent p = new Child();
p.HideF();    // 输出:Parent.HideF()

        这就意味着即使Parent变量p中实际引用的是Child类型的对象,通过p调用的方法还是Parent类的!

因此,结论是:

        当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。

如果确实希望调用的是子类的方法,应先进行强制类型转换:

((Child)p).HideF();

回过头来再看看Parent和Child类,VS在编译这两个类时会发出一个警告。

警告如下:

"HideExamples.Child.HideF()" 隐藏了继承的成员
"HideExamples.Parent.HideF()" 如果是有意隐藏,请使用关键字 new 

虽然上述警告并不影响程序运行结果,却告诉我们代码不符合C#的语法规范,修改Child类的代码以消除上述警告:

class Child : Parent
{
    public new void HideF()
    {
        Console.WriteLine("Child.HideF()");
    }
}

        "new"关键字明确告诉C#编译器:子类会隐藏父类的同名方法,并提供自己的新版本。

由于子类隐藏了父类的同名方法,所以如果要在子类方法的实现代码中调用父类被隐藏的同名方法,请使用base关键字,代码如下:

class Child : Parent
{
    public new void HideF()
    {
        Console.WriteLine("Child.HideF()");
        base.HideF();    // 调用父类被隐藏的方法
    }
}

0.3 方法重写与虚方法调用

        上述由于子类隐藏了父类的同名方法,如果不进行强制转换就无法通过父类变量直接调用子类的同名方法,哪怕父类变量引用的是子类对象。

        我们希望每个对象都职责分明,即如果父类变量引用的是子类对象,则调用的就是子类定义的方法,而如果父类变量引用的是父类对象,调用的就是父类定义的方法。

        为达到这个目的,我们在父类同名方法前加关键字virtual,表明这是一个虚方法,子类可以重写此方法。与此同时,需要在子类同名方法前加关键字override,表明对父类同名方法进行了重写。

示例代码如下:

class Parent
{
	public virtual void OverrideF()
    {
		Console.WriteLine("Parent.OverrideF()");
    }
}

class Child : Parent
{
    public override void OverrideF()
    {
        Console.WriteLine("Child.OverrideF()");
    }
}

//请看以下使用代码
Child c = new Child();
Parent p = c;
p.OverrideF();  // 输出:Child.OverrideF()

        这一示例表明将父类方法定义为虚方法、子类重写同名方法之后,通过父类变量调用此方法,到底是调用父类还是子类的方法,由父类变量引用的真实对象类型决定,而与父类变量无关。

换句话说,同样一句代码:

p.OverrideF();

在p引用不同对象时,其运行的结果可能完全不一样。

        面向对象语言拥有的“虚方法调用特性”,使我们可以只用同样的一个语句,在运行时根据对象类型而执行不同的操作,让代码具有“运行时功能可变”的特性。

0.4 子类父类字段同名布下的陷阱

        如果子类字段与父类字段同名,又会出现什么情况?

代码示例:

class Parent
{
    public int i = 100;
}

class Child : Parent
{
	public int i = 200;
}

//请看以下使用代码
Child c = new Child();
Console.WriteLine(c.i);	//	输出:200
Parent p = new Parent();
Console.WriteLine(p.i); //	输出:100
p = c;  // 父类变量引用子类对象
Console.WriteLine(p.i); //	输出:100

从这段代码中可得出结论:

        如果子类与父类有相同名字的字段,到底使用哪个字段,由对象变量编译时的类型决定,而与程序运行时对象变量引用的真实对象类型无关。

        与方法隐藏类似,Child类直接隐藏Parent类的字段会引发一个C#编译器警告,加一个关键字new就行:

class Child : Parent
{
	public new int i = 200;
}

注意:尽量避免在编程中出现字段隐藏的情况,不要在子类定义与父类同名的字段。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值