提起子类、基类和方法继承这些概念,肯定大家都非常熟悉。毕竟,作为一门支持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之后,有两种函数调用方式。
用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 是虚方法,我们需要考虑对象实例以决定要调用的方法。
怎么样,是不是有小伙伴猜错结果了?
在子类对基类有方法继承、重写和隐藏的情况下,有时候判断具体哪个方法被调用会有难度,但请记住以下要点:
这样,当我们再遇到子类隐藏基类虚方法的情况,应用以上要点就可以拨云见日。
原文作者:老胡写代码
原文地址:https://www.cnblogs.com/deatharthas/p/13378708.html