C#中子类对基类方法的继承、重写和隐藏
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C#中⼦类对基类⽅法的继承、重写和隐藏
提起⼦类、基类和⽅法继承这些概念,肯定⼤家都⾮常熟悉。
毕竟,作为⼀门⽀持OOP的语⾔,掌握⼦类、基类是学习C#的基础。
不过,这些概念虽然简单,但是也有⼀些初学者可能会遇到的坑,我们⼀起看看吧。
⼦类继承基类⾮私有⽅法
⾸先我们看最简单的⼀种,⼦类继承⾃基类,但⼦类对继承的⽅法没有任何改动
class Person
{
public void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
}
class Employee : Person
{
}
class Program
{
static void Main(string[] args)
{
Person p = new Employee();
p.Greeting();
}
}
在这个例⼦中,作为⼦类的Employee⾃动继承了基类的Greeting⽅法,当在⼦类实例调⽤这个⽅法的时候,实际上调⽤的是基类的⽅法。
这个例⼦⾮常简单,⽏庸多⾔。
⼦类覆盖基类⽅法
接着是最常见的情况,⼦类覆盖基类的⽅法,典型的例⼦如下
class Person
{
public virtual void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
}
class Employee : Person
{
public override void Greeting()
{
Console.WriteLine("Hello, I am Employee");
}
}
class Program
{
static void Main(string[] args)
{
Employee e = new Employee();
Person p = e;
p.Greeting();
e.Greeting();
}
}
同样,这段代码也很简单,基类⽅法通过关键字virtual表明⽅法可以被覆盖,⼦类通过关键字override实现对基类⽅法的覆盖,最后看调⽤部分,⽆论变量类型是⼦类还是基类,只要对象实际类型是⼦类,调⽤的⽅法都是⼦类覆盖的⽅法,这也是多态的实现基础。
⼦类隐藏基类⽅法
上⾯两个例⼦都⾮常简单,逻辑也很清楚,有点绕的要算⼦类隐藏基类⽅法的情况。
⼦类隐藏基类的⾮虚⽅法
基类被⼦类继承的⽅法可能是虚⽅法,也可能是⾮虚⽅法,先看⾮虚⽅法被⼦类隐藏的情况,隐藏基类⽅法使⽤的关键字是new
class Person
{
public void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
}
class Employee : Person
{
public new void Greeting()
{
Console.WriteLine("Hello, I am Employee");
}
}
class Program
{
static void Main(string[] args)
{
Employee e = new Employee();
Person p = e;
p.Greeting();
e.Greeting();
}
}
这⾥的结果可能就出乎某些初学者的意料了,为什么明明是⼦类Employee的实例,却在不同的引⽤变量类型下呈现出了不⼀样的效果?为什么会调⽤到了基类⾥⾯的⽅法?
其实这跟C#的函数调⽤机制有关,⼀般来说,C#编译成MSIL之后,有两种函数调⽤⽅式。
Call 以⾮虚的⽅式调⽤⽅法,⼀般⽤于静态函数调⽤,因为静态函数不可能是虚的,但也可以以⾮虚的⽅式调⽤⼀个虚⽅法
Callvirt 以虚⽅式调⽤,⼀般⽤于⾮静态⽅法和虚⽅法的调⽤。
如果调⽤的⽅法⾮虚,则引⽤变量类型决定了最终调⽤的⽅法;反之,如果调⽤的⽅法为虚,则实例变量类型决定最终调⽤的⽅法——因为可能出现⽅法重写,即,多态
⽤ILDASM打开我们的程序集看看,
证明了这⾥确实是⽤的Callvirt,⽽这个⽅法是⾮虚的⽅法,所以在两次调⽤中,引⽤变量类型Person和Employee就能够决定所调⽤的⽅法。
两个类分别实现了⾃⼰的Greeting⽅法,没有出现⼦类覆盖基类⽅法的情况。
这就解释了为什么两次调⽤结果不同。
最后让我们来看看最复杂的⼀种情况
⼦类隐藏基类的虚⽅法
考虑下⾯的代码
class Person
{
public virtual void Greeting()
{
Console.WriteLine("Hello, I am Person");
}
}
class Employee : Person
{
public new virtual void Greeting()
{
Console.WriteLine("Hello, I am Employee");
}
}
class Manager : Employee
{
public override void Greeting()
{
Console.WriteLine("Hello, I am Manager");
}
}
class Program
{
static void Main(string[] args)
{
Manager m = new Manager();
Person p = m;
Employee e = m;
p.Greeting();
e.Greeting();
m.Greeting();
}
}
猜⼀下输出应该是什么?这也是⽼胡曾经遇到过的⼀道笔试题,表⾯看着简单,但是不注意也会掉坑⾥
1,2,3,答案揭晓
是不是有点出乎意料呢,让我们来分析⼀下
⾸先,三次调⽤均是callvirt,⽽且⽅法Greeting是虚⽅法,我们需要考虑对象实例以决定要调⽤的⽅法。
在第⼀次调⽤中,引⽤变量类型是Person,虽然对象实例类型Manger重写了Greeting⽅法,但是它重写的是继承⾃Manger基类Emplyee的Greeting⽅法,Person中Greeting⽅法在⼦类Manger中仅仅是被隐藏⽽没有被重写,所以这⾥调⽤的是Person中的Greeting
⽽第⼆次调⽤中,引⽤变量类型是Employee,Employee的Greeting⽅法被Manager重写,所以这次调⽤到的是Manager中的Greeting 最后⼀次调⽤⽏庸多⾔,简单的重写案例⽽已
怎么样,是不是有⼩伙伴猜错结果了?
总结
在⼦类对基类有⽅法继承、重写和隐藏的情况下,有时候判断具体哪个⽅法被调⽤会有难度,但请记住以下要点:
如果被调⽤⽅法⾮虚,那么只⽤关注引⽤变量类型就好,引⽤变量类型能决定调⽤⽅法在哪⾥
如果调⽤⽅法为虚,我们需要站在引⽤变量类型的⾓度,审视该⽅法是否被对象类型所重写;若是,则调⽤对象类型的重写⽅法;反之,则再次让引⽤变量类型决定调⽤⽅法。
这样,当我们再遇到⼦类隐藏基类虚⽅法的情况,应⽤以上要点就可以拨云见⽇。