C#多态分析

理解多态首先是:

C#继承(C# 编程指南)

类可以从其他类中继承。这是通过以下方式实现的:在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类(即基类)。例如:

public class A
{
public A() { }
}

public class B : A
{
public B() { }
}

新类(即派生类)将获取基类的所有非私有数据和行为以及新类为自己定义的所有其他数据或行为。因此,新类具有两个有效类型:新类的类型和它继承的类的类型。

在上面的示例中,类 B 既是有效的 B,又是有效的 A。访问 B 对象时,可以使用强制转换操作将其转换为 A 对象。强制转换不会更改 B 对象,但您的 B 对象视图将限制为 A 的数据和行为。将 B 强制转换为 A 后,可以将该 A 重新强制转换为 B。并非 A 的所有实例都可强制转换为 B,只有实际上是 B 的实例的那些实例才可以强制转换为 B。如果将类 B 作为 B 类型访问,则可以同时获得类 A 和类 B 的数据和行为。对象可以表示多个类型的能力称为多态性。有关更多信息,请参考这篇文章多态性。

多态的解释:

封装、继承、多态,面向对象的三大特性,前两项理解相对容易,但要理解多态,特别是深入的了解,对于初学者而言可能就会有一定困难了。我一直认为学习OO的最好方法就是结合实践,封装、继承在实际工作中的应用随处可见,但多态呢?也许未必,可能不经意间用到也不会把它跟“多态”这个词对应起来。在此抛砖引玉,大家讨论,个人能力有限,不足之处还请指正。

之前看到过类似的问题:如果面试时主考官要求你用一句话来描述多态,尽可能的精炼,你会怎么回答?当然答案有很多,每个人的理解和表达不尽相同,但我比较趋向这样描述:通过继承实现的不同对象调用相同的方法,表现出不同的行为,称之为多态。

例1:

代码
publicclassAnimal
{
publicvirtualvoidEat()
{
Console.WriteLine("Animaleat");
}
}

publicclassCat:Animal
{
publicoverridevoidEat()
{
Console.WriteLine("Cateat");
}
}

publicclassDog:Animal
{
publicoverridevoidEat()
{
Console.WriteLine("Dogeat");
}
}

classTester
{
staticvoidMain(string[]args)
{
Animal[]animals=newAnimal[3];

animals[0]=newAnimal();
animals[1]=newCat();
animals[2]=newDog();

for(inti=0;i<3;i++)
{
animals[i].Eat();
}
}
}

输出如下:

Animal eat...

Cat eat...

Dog eat...

在上面的例子中,通过继承,使得Animal对象数组中的不同的对象,在调用Eat()方法时,表现出了不同的行为。

多态的实现看起来很简单,要完全理解及灵活的运用c#的多态机制,也不是一件容易的事,有很多需要注意的地方。

1. new的用法

先看下面的例子。

例2

代码
publicclassAnimal
{
publicvirtualvoidEat()
{
Console.WriteLine("Animaleat");
}
}

publicclassCat:Animal
{
publicnewvoidEat()
{
Console.WriteLine("Cateat");
}
}

classTester
{
staticvoidMain(string[]args)
{
Animala=newAnimal();
a.Eat();

Animalac=newCat();
ac.Eat();

Catc=newCat();
c.Eat();
}
}

运行结果为:

Animal eat...

Animal eat...

Cat eat...

可以看出,当派生类Cat的Eat()方法使用new修饰时,Cat的对象转换为Animal对象后,调用的是Animal类中的Eat()方法。其实可以理解为,使用new关键字后,使得Cat中的Eat()方法和Animal中的Eat()方法成为毫不相关的两个方法,只是它们的名字碰巧相同而已。所以, Animal类中的Eat()方法不管用还是不用virtual修饰,也不管访问权限如何,或者是没有,都不会对Cat的Eat()方法产生什么影响(只是因为使用了new关键字,如果Cat类没用从Animal类继承Eat()方法,编译器会输出警告)。

我想这是设计者有意这么设计的,因为有时候我们就是要达到这种效果。严格的说,不能说通过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。

2.override实现多态

真正的多态使用override来实现的。回过去看前面的例1,在基类Animal中将方法Eat()用virtual标记为虚拟方法,再在派生类Cat和Dog中用override对Eat()修饰,进行重写,很简单就实现了多态。需要注意的是,要对一个类中一个方法用override修饰,该类必须从父类中继承了一个对应的用virtual修饰的虚拟方法,否则编译器将报错。

好像讲得差不多了,还有一个问题,不知道你想没有。就是多层继承中又是怎样实现多态的。比如类A是基类,有一个虚拟方法method()(virtual修饰),类B继承自类A,并对method()进行重写(override修饰),现在类C又继承自类B,是不是可以继续对method()进行重写,并实现多态呢?看下面的例子。

例3:

代码
publicclassAnimal
{
publicvirtualvoidEat()
{
Console.WriteLine("Animaleat");
}
}

publicclassDog:Animal
{
publicoverridevoidEat()
{
Console.WriteLine("Dogeat");
}
}

publicclassWolfDog:Dog
{
publicoverridevoidEat()
{
Console.WriteLine("WolfDogeat");
}
}

classTester
{
staticvoidMain(string[]args)
{
Animal[]animals=newAnimal[3];

animals[0]=newAnimal();
animals[1]=newDog();
animals[2]=newWolfDog();

for(inti=0;i<3;i++)
{
animals[i].Eat();
}
}
}

运行结果为:

Animal eat...

Dog eat...

WolfDog eat...

在上面的例子中类Dog继承自类Animal,对方法Eat()进行了重写,类WolfDog又继承自Dog,再一次对Eat()方法进行了重写,并很好地实现了多态。不管继承了多少层,都可以在子类中对父类中已经重写的方法继续进行重写,即如果父类方法用override修饰,如果子类继承了该方法,也可以用override修饰,多层继承中的多态就是这样实现的。要想终止这种重写,只需重写方法时用sealed关键字进行修饰即可。

3. abstract-override实现多态

先在我们在来讨论一下用abstract修饰的抽象方法。抽象方法只是对方法进行了定义,而没有实现,如果一个类包含了抽象方法,那么该类也必须用abstract声明为抽象类,一个抽象类是不能被实例化的。对于类中的抽象方法,可以再其派生类中用override进行重写,如果不重写,其派生类也要被声明为抽象类。看下面的例子。

例4:

代码
publicabstractclassAnimal
{
    public abstract void Eat();
}

publicclassCat:Animal
{
publicoverridevoidEat()
{
Console.WriteLine("Cateat");
}
}

publicclassDog:Animal
{
publicoverridevoidEat()
{
Console.WriteLine("Dogeat");
}
}

publicclassWolfDog:Dog
{
publicoverridevoidEat()
{
Console.WriteLine("Wolfdogeat");
}
}

classTester
{
staticvoidMain(string[]args)
{
Animal[]animals=newAnimal[3];

animals[0]=newCat();
animals[1]=newDog();
animals[2]=newWolfDog();

for(inti=0;i {
animals[i].Eat();
}
}
}

运行结果为:

Cat eat...

Dog eat...

Wolfdog eat...

从上面可以看出,通过使用abstract-override可以和virtual-override一样地实现多态,包括多层继承也是一样的。不同之处在于,包含虚拟方法的类可以被实例化,而包含抽象方法的类不能被实例化。

我的理解:

基于Java的多态性的角度:

普通类的多态:C# virtual关键字+ override关键字配合而来的继承 类似与Java的 上转型对象, 此时的override不是字面意思 "Override(重载)",而是通常概念上的 重写,这个地方是一个误区。

abstract类的多态:abstract类的带有override关键字的实现,这里也是重写,使用上转型对象的方式来调用被override的成员一定调用的是子类的成员。

interface的多态:接口本身没有方法实现部分,使用上转型对象的方式只能调用子类本身的方法。

所以可以得到结论:C#多态中只要有override出现的地方就是重写 !

普通类或abstract类 不使用virtual关键字 或 override 直接覆写父类成员,这里运行可以使用,也是重写,但是编译时会报警告,所以并不是的C#真正的多态。如果要实现C#多态,一定要使用virtual关键字 override关键字.

C#建议 如果需要使用父类的成员,在子类中使用base关键字来调用,不用使用对象来调用。而使用对象来调用被override的成员(重写)。

这样一来,可以理解C#多态与Java多态的区别了:C#多添加virtual 和override关键字来明显区分重载与重写。使用is 和 as关键字来判断类型是否匹配和转换类型。

一些特别研究:

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

然后我们在客户程序中调用这两个方法。
C#多态分析_第1张图片

这时候我们发现智能提示里提示这个方法已经被重载过一次了。这样我们就可以根据业务逻辑调用不同的方法来实现不同的业务。
C#多态分析_第2张图片
C#多态分析_第3张图片

客户端测试程序:

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();

执行结果为:

C#多态分析_第4张图片

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

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次,其中两次是被覆写的
C#多态分析_第5张图片




客户端测试程序

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();

执行结果为:
C#多态分析_第6张图片

这里还有一个需要提的地方就是在子类如果需要访问父类的方法可以使用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类的方法时返回的还是加法运算的结果。
C#多态分析_第7张图片

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

Override覆写

Overload重载

位置

存在于继承关系的类中

存在于同一类中

方法名

相同

相同

参数列表

相同

必须不同

返回值

相同

可以不相同

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

虚方法

声明使用virtual关键字。

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

虚方法可以有实现体。

抽象方法

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

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

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

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

例:

public class TestOverride : Test

{

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

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

}

在编译的时候会出现一个错误
C#多态分析_第8张图片

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

Public abstract class TestOverride : Test

{

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


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

}

这时满足了抽象方法的要求,即可编译通过
C#多态分析_第9张图片

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

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#多态分析)