浅谈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);
}
}
这样我们在客户程序进行访问继承Test类TestOverride类的方法时返回的还是加法运算的结果。
我们来对重载和覆写作一个比较
|
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;
}
}
这样我们就实现了用抽象类来定义操作,用具体的类还根据不同情况实现不同的操作。关于这一点就引出了我们设计模式中创建型模式中的工厂方法、建造者、抽象工厂方法等(这些是我暂时学到的模式),如果大家想了解这些知识可以参考TerryLee的设计模式系列文章,很经典。
到此为止我们介绍了C#中多态性涉及的几个概念,希望对大家的在以后代码结构设计上能有所帮助:)
在这里牢骚几句,现在的项目中变化是一件很平常的事,我们设计的软件就是要适应这种经常的变化,将项目中的一些变化点封装起来,在业务发生变化的时候我们能很轻松的应对这种变化。一个软件的必定会经过它生命周期中的各个部分并最后走向死亡,但是我们可以在设计中通过使软件有能力来应对这些变化来使它的生存期长一些,会为我们带来更多的价值(哈哈,这些其实就是设计模式所要达到的一些目的)。
希望大家能从此文中得到一些收获,谢谢:)
长久以来,多态性一直困扰着我,虽然我知道是怎么回事,但说不出来,也许就是只能意会,不能言传吧,
通过继承,一个类可以用作多种类型 :可以用作它自己的类型,也可以用作基类型,或者在实现接口时用作任何接口类型,这就成为多态性。
C# 中每种类型都是多态的,类型可用作他们自己的类型或用作Object的实例,因为任何类型都自动将Object当作基类型。
多态性不仅对派生类很重要,对基类也很 重要,在任何情况下,使用基类实际上是都可能是在使用已经强制转化成基类类型的派生类对象,基类的设计者预测到其基类中可能会在派生类中发生更改的方面。
当派生类从基类继承时,它会获得所有基类的方法,属性等,若要改变基类的和行为,有两种方法:一是使用新的派生成员替代基成员,二是重写基类中的虚拟成员。
使用新的派生成员替换基成员使用关键字new,如果基类定义了一个方法或字段,则new关键字创建该方法或字段的新定义,new关键字要放在返回值类型之前。
如:
class BaseClass
{
public void BaseMethod()
{ }
}
class DerivedClass : BaseClass
{
public new void BaseMethod()
{ }
}
使用 new 关键字时,调用的是新
的类成员而不是已被替换的基类成员。这些基类成员称为隐藏成员。如果将派生类的实例强制转换为基类的实例,就仍然可以调用隐藏类成员。
为了使派生类的实例完全接替来自基类的类成员,基类必须将该成员声明为虚拟的。这是通过在该成员的返回类型之前添加 virtual 关键字来实现的。然后,派生类可以选择使用 override 关键字而不是 new,将基类实现替换为它自己的实现。
如:
public class BaseClass
{
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override int WorkProperty
{
get { return 0; }
}
}
字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员
使用虚拟方法和属性可以预先计划未来的扩展。由于在调用虚拟成员时不考虑调用方正在使用的类型,所以派生类可以选择完全更改基类的外观行为。
无论在派生类和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员都将永远为虚拟成员
本文来自****博客,转载请标明出处:http://blog.****.net/luqinghua/archive/2007/04/03/1550262.aspx