多态
一.多态的概念
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。同一个类型的实例调用“相同”的方法,产生的结果是不同的。
1.重载overload:在同一个作用域(一般指一个类)的两个或多个方法函数名相同,参数列表不同的方法叫做重载。特点:
- 相同的方法名
- 不同的参数列表
- 可以不相同的返回值类型
public void Like()
{
Console.WriteLine("Like everything");
}
public string Like(string name)
{
Console.WriteLine($"Like {name}");
return name;
}
2.重写override:子类中为满足自己的需要来重复定义某个方法的不同实现,需要用override关键字,被重写的方法必须是虚方法,用的是virtual关键字。特点:
- 相同的方法名
- 相同的参数列表
- 相同的返回值
public class Test
{
public virtual void Sleep()
{
Console.WriteLine("睡着了。。。");
}
}
public class TestSon:Test
{
public override void Sleep()
{
//base.Sleep();
Console.WriteLine("在床上睡着了。。。");
}
}
////////////////////////////////////////////
Test t1 = new Test();
Test t2 = new TestSon();
TestSon ts1 = new TestSon();
t1.Sleep();//睡着了。。。
t2.Sleep();//在床上睡着了。。。
ts1.Sleep();//在床上睡着了。。。
//重写方法中子类的变量调用子类重写的方法
//父类的变量如果引用的是父类的实例那么调用父类的方法,引用的是子类的实例则调用子类的方法。
3.虚方法:基类中定义的允许在派生类中重写的方法,使用virtual关键字定义。
虚方法可以被直接调用。
public virtual void Sleep()
{
Console.WriteLine("睡着了。。。");
}
public void Show()
{
this.Sleep();
}
4.抽象方法:在基类中定义的并且必须在派生类中重写的方法,使用abstract关键字定义。
抽象方法只能在抽象类中定义。
public abstract class Testt
{
public abstract void Sleep();
}
public class TesttSon : Testt
{
public override void Sleep()
{
Console.WriteLine("必须重写抽象方法");
//throw new NotImplementedException();
}
}
注:虚方法和抽象方法的区别,因为抽象类无法实例化,所以抽象方法没有办法被调用,也就是说抽象方法永远不可能被实现。
5.隐藏方法:在派生类中定义的和基类中的某个方法同名的方法,使用new关键字定义。
public class Testtt
{
public void Sleep()
{
Console.WriteLine("这是基类Sleep方法");
}
public virtual void Eat()
{
Console.WriteLine("这是基类Eat方法");
}
}
public class TestttSon : Testtt
{
public new void Sleep()
//new和public顺序可更换
{
Console.WriteLine("这是子类Sleep隐藏方法");
}
public new void Eat() //基类中的虚方法和非虚方法都可以隐藏。
{
Console.WriteLine("这是子类Eat隐藏方法");
}
}
///////////////////////////////////////////////
Testtt t1 = new Testtt();
Testtt t2 = new TestttSon();
TestttSon ts1 = new TestttSon();
t1.Sleep();//这是基类Sleep方法
t2.Sleep();//这是基类Sleep方法
ts1.Sleep();//这是子类Sleep隐藏方法
//隐藏方法中基类的实例调用基类的方法,子类的实例调用子类的方法。
二.深入理解多态性
要深入理解多态性,就要先从值类型和引用类型说起。我们都知道值类型是保存在线程栈上的,而引用类型是保存在托管堆中的。因为所有的类都是引用类型,所以我们仅仅看引用类型。
现在回到刚才的例子,Main函数时程序的入口,在JIT编译器将Main函数编译为本地CPU指定时,CLR会创建几个实例来表示引用的类型本身,我们把它称之为“类型对象”。该对象包含了类中的静态字段,以及包含类中所有方法的方法表,还包含了托管堆中所有对象都要有的两个额外的成员——类型对象指针(Type Object Point)和同步块索引(sync Block Index)。
public class Animal
{
public void Live(){ }
public virtual void EatFood(){ }
public void Sleep(){ }
}
public class Cat : Animal
{
public override void EatFood(){ }
public new void Sleep(){ }
}
public class Dog : Animal
{
public override void EatFood(){ }
}
/////////////////////////////////////////
//Animal的实例
Animal a = new Animal();
//Animal的实例,引用子类Cat对象
Animal ac = new Cat();
//Animal的实例,引用子类Dog对象
Animal ad = new Dog();
a.Sleep();
a.EatFood();
ac.EatFood();
ad.EatFood();
下面实例化三个Animal实例,但是他们实际上指向的分别是Animal对象、Cat对象和Dog对象,如下图:
请注意,变量ac和ad虽然都是Animal类型,但是指向的分别是Cat对象和Dog对象,这里是关键。
当执行a.Sleep()时,由于Sleep是非虚实例方法,JIT编译器会找到发出调用的那个变量(a)的类型(Animal)对应的类型对象(Animal类型对象)。然后调用该类型对象中的Sleep方法,如果该类型对象没有Sleep方法,JIT编译器会回溯类的基类(一直到Object)中查找Sleep方法。
当执行ac.EatFood时,由于EatFood是虚实例方法,JIT编译器调用时会在方法中生成一些额外的代码,这些代码会首先检查发出调用的变量(ac),然后跟随变量的引用地址找到发出调用的对象(Cat对象),找到发出调用的对象对应的类型对象(Cat类型对象),最后在该类型对象中查找EatFood方法。同样的,如果在该类型对象中没有查找到EatFood方法,JIT编译器会回溯到该类型对象的基类中查找。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义