多态性





        面向对象的第三个主要特性。


        多态性是同一操作作用于不同的类的对象,由不同的类进行不同的解释,最终产生不同的执行效果。


        多态性按照产生多态行为的时间分为:编译时多态与运行时多态。


1  编译时多态性


        编译时多态是指通过设置不同的方法签名,在编译时由编译器根据方法的签名决定调用何种方法。


        通过指定方法的访问级别(例如 public private)、可选修饰符(例如 abstract sealed)、返回值、名称和任何方法参数,可以在类或结构中声明方法。这些部分统称为方法的“签名”。


        使用不同的方法签名实现多态性称为方法重载。为进行方法重载,方法的返回类型不是方法签名的一部分。但是,在确定委托和委托所指向方法之间的兼容性时,返回类型是方法签名的一部分。


        安装具体签名的不同可以分为:参数类型或数量重载、输出与输入型参数引用重载。


1.1 参数类型或数量重载


        在方法重载时,实参与形参一一对应,如果相应位置类型相同且方法参数数量相等,则完成匹配,即实现同名方法不同参数的重载。


如有下面方法:

		void method()
        {
            Console.WriteLine("is a zero parameter");
        }
        void method(int i)
        {
            Console.WriteLine("is a one parameter");
        }
        void method(int i, double d)
        {
            Console.WriteLine("is a two parameter");
        }

当调用函数

		this.method(1, 2.0);

会调用第三个方法,输出:is a two parameter


1.2 输出与输入型参数引用重载


        输入引用从参数使用 ref关键字修饰,在方法调用传递过程中,不同于值传递,传递的是值在内存中的地址,由此,在方法内对参数的修改会作用至实参本身。


        输出引用参数使用 out关键字修饰,类似于输入引用参数,但在调用方法前不需要对变量进行初始化。


        由于本篇的重点是运行时多态,在对编译时多态介绍比较简单,仅作参考。


2  运行时多态性


        运行时多态指在基本类中定义虚方法,在派生类中重新定义方法或覆盖虚方法,在发生对象调用时,由公共语言运行时(CLR)根据对象类型决定调用何种方法。


        虚方法允许您以统一方式处理多组相关的对象。由虚方法引出的多态性有多重情形,以下着重介绍3种情形。


2.1 新成员隐藏基类成员


        通过 new关键字,在派生类中重新定义一个与基类相同名字方法,在使用该派生类的实例被当作基类的实例访问,会调用基类成员方法。

如下示例:

	public class BaseClass
    {
        public void DoWork() 
        {
            Console.WriteLine("base Class do work");
        }
       
    }

    public class DerivedClass : BaseClass
    {
        public new void DoWork() 
        {
            Console.WriteLine("derived Class do work");
        }
        
    }

执行以下语句时,

			DerivedClass D = new DerivedClass();
            D.DoWork();

            BaseClass B = (BaseClass)D;
            B.DoWork();

会输出如下结果。


derivedClass do work


baseClass do work


        程序表面,当派生类DerivedClass实例 D被当做基类BaseClass实例 B调用方法 DoWork()时,调用的是基类 baseClassDoWork()方法。


2.2 虚成员


        使用virtual在基类中定义虚成员方法,使用override在派生类中覆盖虚方法,从而使得在使用该派生类的实例被当作基类的实例访问,会调用派生类成员方法。


如下示例:

	public class BaseClass
    {
        public virtual void DoWork() 
        {
            Console.WriteLine("base Class do work");
        }
       
    }

    public class DerivedClass : BaseClass
    {
        public override void DoWork() 
        {
            Console.WriteLine("derived Class do work");
        }
        
    }


执行以下语句时,

			DerivedClass D = new DerivedClass();
            D.DoWork();

            BaseClass B = (BaseClass)D;
            B.DoWork();

会输出如下结果。


derivedClass do work


derivedClass do work


        程序表面,当派生类中对基类方法进行覆盖,派生类的实例 D被当做基类实例 B调用方法时,仍然调用的是派生类DoWork()方法。


2.3 阻止派生类重写虚拟成员


        使用关键字sealed阻止派生类重写虚方法,基类至sealed修饰的派生类将继续沿用成员继承机制,而对sealed修饰的派生类再派生时,将使用新成员隐藏基类成员机制。


示例如下:

	public class A
    {
        public virtual void DoWork() 
        {
            Console.WriteLine("a do work");
        }
    }
    public class B : A
    {
        public override void DoWork() 
        {
            Console.WriteLine("b do work");
        }
    }
    public class C : B
    {
        public sealed override void DoWork() 
        {
            Console.WriteLine("c do work");
        }
    }
    public class D : C
    {
        public new void DoWork() 
        {
            Console.WriteLine("d do work");
        }
    }

执行以下语句时,

			D d = new D();
            Console.WriteLine("d");
            d.DoWork();
            C c = (C)d;
            Console.WriteLine("c");
            c.DoWork();
            B b = (B)d;
            Console.WriteLine("b");
            b.DoWork();
            A a = (A)d;
            Console.WriteLine("a");
            a.DoWork();

会输出如下结果。


d


d do work


c


c do work


b


c do work


a


c do work


        程序表明,派生类实例对象d被当做基类实例对象c调用DoWork()方法时,执行的是基类 c的方法;派生类实例对象d被当做基类实例对象b调用DoWork()方法时,执行的是基类 c的方法;派生类实例对象d被当做基类实例对象a调用DoWork()方法时,执行的是基类 c的方法,符合新成员隐藏基成员、虚方法机制。


        在此不妨谈谈其具体实现机制,当派生类对象被强制类型转换赋值给基类对象时(这里有个著名的里氏法则,子类都可以向父类转化,反之不行,具体证明我也不会),基类对象是拿着派生类的引用,也就是说地址还指向派生类的对象,当使用override覆盖时,派生类对继承过来的基类成员会重新定义,也就是派生类中的基类副本会发生改变,但这并不会影响基类本身的定义,因而当派生类对象被当做基类对象访问时,是基类调用,但是是派生类内的基类发生调用,因而也就调用的是我们在派生类中定义的覆盖成员;而使用 new定义新的成员并不会改变派生类中基类的副本,因而当派生类对象被当做基类对象访问时,还是由派生类中的基类副本,调用其原本的基类中的成员;而密封不过是阻止了虚函数在派生类中被覆盖,并没有从根本上引入新的机制。由于认知有限,难免让各位见笑了,如有高见,可以留言。


参考


C# 编程指南


C# 自学手册





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值