博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#多态性详解
阅读量:4072 次
发布时间:2019-05-25

本文共 7746 字,大约阅读时间需要 25 分钟。

文章部分内容来自:作者: 出处:

一、多态的概念
        首先解释下什么叫多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。换句话说,实际上就是同一个类型的实例调用“相同”的方法,产生的结果是不同的。这里的“相同”打上双引号是因为这里的相同的方法仅仅是看上去相同的方法,实际上它们调用的方法是不同的。 
 
        说到多态,我们不能免俗的提到下面几个概念:重载、重写、虚方法、抽象方法以及隐藏方法。下面就来一一介绍他们的概念。
        1、
重载(overload):在同一个作用域(一般指一个类)的两个或多个方法函数名相同,参数列表不同的方法叫做重载,它们有三个特点(俗称两必须一可以):
  • 方法名必须相同
  • 参数列表必须不相同
  • 返回值类型可以不相同
        如:
public void Sleep()        {            Console.WriteLine("Animal睡觉");        }        public int Sleep(int time)        {            Console.WriteLine("Animal{0}点睡觉", time);            return time;        }

 

        2、
重写(override):子类中为满足自己的需要来重复定义某个方法的不同实现,需要用override关键字,被重写的方法必须是虚方法,用的是virtual关键字。它的特点是(三个相同):
  • 相同的方法名
  • 相同的参数列表
  • 相同的返回值。
如:父类中的定义:
public virtual void EatFood()        {            Console.WriteLine("Animal吃东西");        }

       子类中的定义:

public override void EatFood()        {            Console.WriteLine("Cat吃东西");            //base.EatFood();        }
tips:经常有童鞋问重载和重写的区别,而且网络上把这两个的区别作为C#做常考的面试题之一。实际上这两个概念完全没有关系,仅仅都带有一个“重”字。他们没有在一起比较的意义,仅仅分辨它们不同的定义就好了。
        
        3、
虚方法:即为基类中定义的允许在派生类中重写的方法,使用virtual关键字定义。如:
public virtual void EatFood()        {            Console.WriteLine("Animal吃东西");        }

 

        注意:虚方法也可以被直接调用。如:
Animal a = new Animal();            a.EatFood();

 

        运行结果:

                
 
        4、
抽象方法:在基类中定义的并且必须在派生类中重写的方法,使用abstract关键字定义。如:
public abstract class Biology    {        public abstract void Live();    }    public class Animal : Biology    {        public override void Live()        {            Console.WriteLine("Animal重写的抽象方法");            //throw new NotImplementedException();        }     }

 

 
        注意:抽象方法只能在抽象类中定义,如果不在抽象类中定义,则会报出如下错误:     
            

 
虚方法和抽象方法的区别是:因为抽象类无法实例化,所以抽象方法没有办法被调用,也就是说抽象方法永远不可能被实现。
 
        5、
隐藏方法:在派生类中定义的和基类中的某个方法同名的方法,使用new关键字定义。如在基类Animal中有一方法Sleep():
public void Sleep()        {            Console.WriteLine("Animal Sleep");        }

 

            则在派生类Cat中定义隐藏方法的代码为:
new public void Sleep()        {            Console.WriteLine("Cat Sleep");        }

 

            或者为:
public new void Sleep()        {            Console.WriteLine("Cat Sleep");        }

 

        注意:(1)隐藏方法不但可以隐藏基类中的虚方法,而且也可以隐藏基类中的非虚方法。
                  (2)隐藏方法中父类的实例调用父类的方法,子类的实例调用子类的方法。
                  (3)和上一条对比:重写方法中子类的变量调用子类重写的方法,父类的变量要看这个父类引用的是子类的实例还是本身的实例,如果引用的是父类的实例那么调用基类的方法,如果引用的是派生类的实例则调用派生类的方法。
 
        好了,基本概念讲完了,下面来看一个例子,首先我们新建几个类:
public abstract class Biology    {        public abstract void Live();    }    public class Animal : Biology    {        public override void Live()        {            Console.WriteLine("Animal重写的Live");            //throw new NotImplementedException();        }        public void Sleep()        {            Console.WriteLine("Animal Sleep");        }        public int Sleep(int time)        {            Console.WriteLine("Animal在{0}点Sleep", time);            return time;        }        public virtual void EatFood()        {            Console.WriteLine("Animal EatFood");        }    }    public class Cat : Animal    {        public override void EatFood()        {            Console.WriteLine("Cat EatFood");            //base.EatFood();        }        new public void Sleep()        {            Console.WriteLine("Cat Sleep");        }        //public new void Sleep()        //{        //    Console.WriteLine("Cat Sleep");        //}    }    public class Dog : Animal    {        public override void EatFood()        {            Console.WriteLine("Dog EatFood");            //base.EatFood();        }    }

 

 
        下面来看看需要执行的代码:
class Program    {        static void Main(string[] args)        {            //Animal的实例            Animal a = new Animal();            //Animal的实例,引用派生类Cat对象            Animal ac = new Cat();            //Animal的实例,引用派生类Dog对象            Animal ad = new Dog();            //Cat的实例            Cat c = new Cat();            //Dog的实例            Dog d = new Dog();            //重载            a.Sleep();            a.Sleep(23);            //重写和虚方法            a.EatFood();            ac.EatFood();            ad.EatFood();            //抽象方法            a.Live();            //隐藏方法            a.Sleep();            ac.Sleep();            c.Sleep();            Console.ReadKey();        }    }

 

 
        首先,我们定义了几个我们需要使用的类的实例,需要注意的是
            (1)Biology类是抽象类,无法实例化;
            (2)变量ac是Animal的实例,但是指向一个Cat的对象。因为Cat类型是Animal类型的派生类,所以这种转换没有问题。这也是多态性的重点。
 
        下面我们来一步一步的分析:
            (1)
//重载            a.Sleep();            a.Sleep(23);

 

        很明显,Animal的变量a调用的两个Sleep方法是重载的方法,第一句调用的是无参数的Sleep()方法,第二句调用的是有一个int 参数的Sleep方法。注意两个Sleep方法的返回值不一样,这也说明了重写的三个特征中的最后一个特征——返回值可以不相同。
        运行的结果如下:

        
        (2)
//重写和虚方法            a.EatFood();            ac.EatFood();            ad.EatFood();

 

        在这一段中,a、ac以及ad都是Animal的实例,但是他们引用的对象不同,a引用的是Animal对象,ac引用的是Cat对象,ad引用的是Dog对象,这个差别会造成执行结果的什么差别呢,请看执行结果:

        
        第一句Animal实例,直接调用Animal的虚方法EatFood,没有任何问题。
        在第二、三句中,虽然同样是Animal的实例,但是他们分别指向Cat和Dog对象,所以调用的Cat类和Dog类中各自重写的EatFood方法,就像是Cat实例和Dog实例直接调用EatFood方法一样。这个也就是多态性的体现:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
 
        (3)
//抽象方法            a.Live();

 

        这个比较简单,就是直接重写父类Biology中的Live方法,执行结果如下:

 
        (4)
//隐藏方法            a.Sleep();            ac.Sleep();            c.Sleep();

 

        在分析隐藏方法时要和虚方法、重写相互比较。变量 a 调用 Animal 类的 Sleep 方法以及变量 c 调用 Cat 类的 Sleep 方法没有异议,但是变量 ac 引用的是一个 Cat 类型的对象,它应该调用 Animal 类型的 EatFood 方法呢,还是 Cat 类型的 EatFood 方法呢?答案是调用父类即Animal的EatFood方法。执行结果如下:

 
 
二、接口的多态性

把动物“吃”的方法放到一个接口(IAnimal)里,然后让具体的动物类(Wolf/Sheep)继承这个接口,并根据自己的需要实现这个接口。

代码实现:

class Program {
static void Main(string[] args) {
new Wolf().Eat(); new Sheep().Eat(); } } public class Wolf : IAnimal {
//多态实现 public void Eat() {
Console.WriteLine("狼吃肉!"); } } public class Sheep : IAnimal {
//多态实现 public void Eat() {
Console.WriteLine("羊吃草!"); } } //接口 public interface IAnimal {
void Eat(); }

接口的多态性就是当不同的类继承了相同的接口以后,都要根据自己的需要重新实现继承的接口,这样同样的方法签名在不同的类中就会实现不同的操作。

三、继承的多态性

2.1.通过虚拟方法实现的多态(virtual,override)

   首先要在基类中实现virtual方法,然后在派生类中根据自己的需要用override重写virtual方法。如果不希望这个方法被继续重写,则把这个方法写成sealed方法

  virtual方法必须在基类中实现。

  代码实现:

class Program {
static void Main(string[] args) {
new Wolf().Eat(); new Sheep().Eat(); new Goat().Eat(); } } public class Wolf : Animal {
//多态实现 public override void Eat() {
base.Eat(); Console.WriteLine("狼吃肉!"); } } public class Sheep : Animal {
//多态实现 public override void Eat() {
base.Eat(); Console.WriteLine("羊吃草!"); } } public class Goat : Sheep {
//多态实现被终结,此Eat方法不能被override,因为用sealed了 public sealed override void Eat() {
//base.Eat(); Console.WriteLine("山羊吃草!"); } } //基类实现虚方法 public class Animal {
public virtual void Eat() { } }

2.2.通过抽象方法实现的多态(abstract,override)

抽象方法必须定义在抽象类里。抽象类不能被创建实例。

基类中的抽象方法只能被声明,不需要实现,所以派生类中重写抽象方法的时候没有base方法。

代码实现如下:

class Program {
static void Main(string[] args) {
new Wolf().Eat(); new Sheep().Eat(); new Goat().Eat(); } } public class Wolf : Animal {
//多态实现 public override void Eat() {
Console.WriteLine("狼吃肉!"); } } public class Sheep : Animal {
//多态实现 public override void Eat() {
Console.WriteLine("羊吃草!"); } } public class Goat : Sheep {
//多态实现被终结,此Eat方法不能被override,因为用sealed了 public sealed override void Eat() {
Console.WriteLine("山羊吃草!"); } } //基类只需声明方法 public abstract class Animal {
public abstract void Eat(); }

四、总结:

1.虚方法重写的时候可以有base方法(base.Eat()),抽象方法重写的时候没有base方法,原因是:虚方法必须在基类中实现,抽象方法只在基类中声明,不需要实现。

2.派生类中可以 不重写虚方法的实现,但是派生类必须重写抽象方法的实现,原因同1.

3.包含虚方法的非抽象类可以被创建实例(对象),但是包含抽象方法的抽象类不能被创建实例。

4.继承接口的派生类必须实现接口的方法,因为接口也是只负责声明方法,不负责实现。

5.接口的多态性不需要用override重写方法。

转载地址:http://pfrji.baihongyu.com/

你可能感兴趣的文章
简述Session 、Cookie、cache 区别
查看>>
CROS实现跨域时授权问题(401错误)的解决
查看>>
Hadoop之基础篇
查看>>
【转】alpha版、beta版、rc版的意思
查看>>
测试方法
查看>>
常见的图片格式及特点
查看>>
Android自定义View仿QQ计步器
查看>>
最简单易懂的设计模式——工厂模式
查看>>
最简单易懂的设计模式——建造者模式
查看>>
Android 解决TextView设置文本和富文本SpannableString自动换行留空白问题
查看>>
最完整的Java IO流学习总结
查看>>
Android开发中Button按钮绑定监听器的方式完全解析
查看>>
解决ScrollView嵌套ListView后,进入页面不从顶部开始
查看>>
基于Rxjava2的事件总线:Rxbus
查看>>
Android6.0动态权限获取框架:RxPermission(基于RxJava2)
查看>>
Android中解决华为手机设置PopupWindow半透明背景无效果问题
查看>>
解决三星note3调用系统拍照后程序崩溃或无法获取图片
查看>>
序列化Serializable和Parcelable的区别
查看>>
Android自定义View绘制真正的居中文本
查看>>
Android贝塞尔曲线实现加入购物车抛物线动画
查看>>