xieex's blog


从事软件开发的交流平台

我的人生信念:态度决定一切
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C#中的多态性

Posted on 2009-03-27 16:28  xieex  阅读(961)  评论(1编辑  收藏  举报

通过C#来温习一下面向对象的多态性:

首先理解一下什么叫多态。同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。

多态性通过派生类覆写基类中的虚函数型方法来实现。

多态是其一种性质,并不是一种实现方法。

多态性分为两种,一种是编译时的多态性,一种是运行时的多态性。

编译时的多态性:编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

运行时的多态性:运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中运行时的多态性是通过覆写虚成员实现。

 

下面我们来分别说明一下多态中涉及到的四个概念:重载,重写,虚方法、抽象方法和隐藏。

重载和覆写的区别:

重载

类中定义的方法的不同版本

                public int Calculate(int x, int y)

                public double Calculate(double x, double y)

特点(两必须一可以)

        方法名必须相同

        参数列表必须不相同

        返回值类型可以不相同

重写

       子类中为满足自己的需要来重复定义某个方法的不同实现。

        通过使用override关键字来实现重写。

        只有虚方法和抽象方法才能被重写。

        要求(三相同)

                相同的方法名称

                相同的参数列表

                相同的返回值类型

 

一、重载:

例:

        public class Test

        {

                public int Calculate(int x, int y)

                {

                        return x + y;

                }

 

                public double Calculate(double x, double y)

                {

                        return x + y;

                }

        }

 

首先看这个类,我们在同一个类中满足了重载的三个特点,方法名必须相同Calculate;参数列表必须不相同第一个方法的两个参数类型为int类型,第二个方法的两个参数类型为double类型;返回值类型可以不相同,一个返回值类型为int,另一个返回值类型为double

然后我们在客户程序中调用这两个方法。

 

 

这时候我们发现智能提示里提示这个方法已经被重载过一次了。这样我们就可以根据业务逻辑调用不同的方法来实现不同的业务。

客户端测试程序

            Test t = new Test();

                        int x;

                        int y;

                        Console.WriteLine("Please input an integer.\n");

                        x = Convert.ToInt32(Console.ReadLine());

                        Console.WriteLine("Please input another integer.\n");

                        y = Convert.ToInt32(Console.ReadLine());

                        Console.WriteLine("Test class Calculate method result.\n");

                        int result1 = t.Calculate(x,y);

                        Console.WriteLine("int x + int y = {0}\n",result1.ToString());

                        double a;

                        double b;

                        Console.WriteLine("Please input an double.\n");

                        a = Convert.ToDouble(Console.ReadLine());

                        Console.WriteLine("Please input another double.\n");

                        b = Convert.ToDouble(Console.ReadLine());

                        Console.WriteLine("Test class Calculate method result.\n");

                        double result2 = t.Calculate(a,b);

                        Console.WriteLine("double x + double y = {0}\n",result2.ToString());

               Console.ReadLine();

执行结果为:

 

 

二、重写

下面来看一看重写,我们将基类(父类)作一下修改

        public class Test

        {

                public virtual int Calculate(int x, int y)

                {

                        return x + y;

                }

 

                public virtual double Calculate(double x, double y)

                {

                        return x + y;

                }

        }

 

派生类(子类)

        public class TestOverride : Test

        {

                public override int Calculate(int x, int y)

                {

                        return x * y;

                }

 

                public override double Calculate(double x, double y)

                {

                        return x * y;

                }

        }

 

这时我们会在客户端的测试程序中发现,我们既可以访问到覆写后的方法也能访问到覆写前基类的方法,显示被重载3次,其中两次是被重写的。

 

客户端测试程序

                        TestOverride t = new TestOverride();

                        int x;

                        int y;

                        Console.WriteLine("Please input an integer.\n");

                        x = Convert.ToInt32(Console.ReadLine());

                        Console.WriteLine("Please input another integer.\n");

                        y = Convert.ToInt32(Console.ReadLine());

                        Console.WriteLine("Test class Calculate method result.\n");

                        int result1 = t.Calculate(x,y);

                        Console.WriteLine("int x * int y = {0}\n",result1.ToString());

                        double a;

                        double b;

                        Console.WriteLine("Please input an double.\n");

                        a = Convert.ToDouble(Console.ReadLine());

                        Console.WriteLine("Please input another double.\n");

                        b = Convert.ToDouble(Console.ReadLine());

                        Console.WriteLine("Test class Calculate method result.\n");

                        double result2 = t.Calculate(a,b);

                        Console.WriteLine("double x * double y = {0}\n",result2.ToString());

 

                        Console.ReadLine();

执行结果为:

 

这里还有一个需要提的地方就是在子类如果需要访问父类的方法可以使用base关键字,例如:

        public class TestOverride : Test

        {

                public override int Calculate(int x, int y)

                {

                        return base.Calculate (x, y);

                }

 

                public override double Calculate(double x, double y)

                {

                        return base.Calculate (x, y);

                }

        }

 

这样我们在客户程序进行访问继承TestTestOverride的方法时返回的还是加法运算的结果。

另外,如果将派生类对象,赋给基类的对象,那么基类对象调用重写方法时,执行的仍然是派生类的方法。

 

Code

结果输出为:

B.F

B.F

我们来对重载和重写作一个比较

 

Override重

Overload重载

位置

存在于继承关系的类中

存在于同一类中

方法名

相同

相同

参数列表

相同

必须不同

返回值

相同

可以不相同

 

最后再来介绍一下虚方法和抽象方法

三、虚方法

声明使用virtual关键字。

调用虚方法,运行时将确定调用对象是什么类的实例,并调用适当的覆写的方法。

虚方法可以有实现体。

四、抽象方法

必须被派生类重写的方法。

可以看成是没有实现体的虚方法。

如果类中包含抽象方法,那么类就必须定义为抽象类,不论是否还包含其他一般方法。

 

关于虚方法,上面我们做了一些实验,下面来看一个关于抽象方法的例子。

例:

        public class TestOverride : Test

        {

                public abstract int Calculate(int x, int y);

                public abstract double Calculate(double x, double y);

        }

在编译的时候会出现一个错误:

 

 

 

要求抽象方法必须被定义在抽象类中,我们作如下修改

        Public abstract class TestOverride : Test

        {

                public abstract int Calculate(int x, int y);

public abstract double Calculate(double x, double y);

        }

这时满足了抽象方法的要求,即可编译通过。

 

同样我们还可以对其进行重写,其实这里的重写就是我们所说的对于一个抽象的具体实现。如上面例子所示,我们先用一个抽象类定义了一个测试类并在其中定义了这个类可以做两个整数或者实数的计算操作,至于具体做什么样的计算和怎么计算并没有定义,只是声明我能做这些事,然后在它的子类(一个具体的类)中实现了他的定义,我们要做的是乘法计算。我们还可以通过另一个具体的类来定义,我们可能要做的不是乘法计算而是加法计算。代码如下:

        public class TestAdd : Test

        {

                public override int Calculate(int x, int y)

                {

                        return x + y;

                }

 

                public override double Calculate(double x, double y)

                {

                        return x + y;

                }

        }

这样我们就实现了用抽象类来定义操作,用具体的类还根据不同情况实现不同的操作。关于这一点就引出了我们设计模式中创建型模式中的工厂方法、建造者、抽象工厂方法等。

 

五、隐藏:

最后来谈一下隐藏(new):

隐藏:基类方法不做申明(默认为非虚方法),在派生类中使用new声明此方法的隐藏。

隐藏时,访问父类则调用父类的方法,子类子类的方法。

隐藏(new)示例:

    class A

    {

        public virtual void F()

        {

            Console.WriteLine("A.F");

        }

    }

    class B : A

    {

        public new void F()

        {

            Console.WriteLine("B.F");

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

 

            B b = new B();

            b.F();

            A a = b;

            a.F(); 

 

        }

    }

 

结果输出为 

   B.F 

   A.F 

如果将new改为override,输出结果为:

 

   B.F 

   B.F

 

到此为止介绍了C#中多态性涉及的几个概念,希望对大家的在以后代码结构设计上能有所帮助。

现在的项目中变化是一件很平常的事,我们设计的软件就是要适应这种经常的变化,将项目中的一些变化点封装起来,在业务发生变化的时候我们能很轻松的应对这种变化。一个软件的必定会经过它生命周期中的各个部分并最后走向死亡,但是我们可以在设计中通过使软件有能力来应对这些变化来使它的生存期长一些,会为我们带来更多的价值,我想这就是设计模式所要达到的一些目的。