多态是同一个行为具有多个不同表现形式或形态的能力。
多态性
意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在
静态多态性
中,函数的响应是在编译时发生的。在
动态多态性
中,函数的响应是在运行时发生的。
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
现实中,比如我们按下 F1 键这个动作:
如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
如果当前在 Word 下弹出的就是 Word 帮助;
在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果。
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
运算符重载
运算符重载将在下一章节讨论,接下来我们将讨论函数重载。
您可以在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。
下面的实例演示了几个相同的函数
Add()
,用于对不同个数参数进行相加处理:
using
System
;
namespace
PolymorphismApplication
public
class
TestData
public
int
Add
(
int
a,
int
b,
int
c
)
return
a
+
b
+
c
;
public
int
Add
(
int
a,
int
b
)
return
a
+
b
;
class
Program
static
void
Main
(
string
[
]
args
)
TestData dataClass
=
new
TestData
(
)
;
int
add1
=
dataClass
.
Add
(
1
,
2
)
;
int
add2
=
dataClass
.
Add
(
1
,
2
,
3
)
;
Console
.
WriteLine
(
"add1 :"
+
add1
)
;
Console
.
WriteLine
(
"add2 :"
+
add2
)
;
下面的实例演示了几个相同的函数
print()
,用于打印不同的数据类型:
using
System
;
namespace
PolymorphismApplication
class
Printdata
void
print
(
int
i
)
Console
.
WriteLine
(
"输出整型: {0}"
, i
)
;
void
print
(
double
f
)
Console
.
WriteLine
(
"输出浮点型: {0}"
, f
)
;
void
print
(
string
s
)
Console
.
WriteLine
(
"输出字符串: {0}"
, s
)
;
static
void
Main
(
string
[
]
args
)
Printdata p
=
new
Printdata
(
)
;
// 调用 print 来打印整数
p
.
print
(
1
)
;
// 调用 print 来打印浮点数
p
.
print
(
1.23
)
;
// 调用 print 来打印字符串
p
.
print
(
"Hello Runoob"
)
;
Console
.
ReadKey
(
)
;
当上面的代码被编译和执行时,它会产生下列结果:
输出整型: 1
输出浮点型: 1.23
输出字符串: Hello Runoob
动态多态性
C# 允许您使用关键字
abstract
创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。
抽象类
包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。
请注意,下面是有关抽象类的一些规则:
您不能创建一个抽象类的实例。
您不能在一个抽象类外部声明一个抽象方法。
通过在类定义前面放置关键字
sealed
,可以将类声明为
密封类
。当一个类被声明为
sealed
时,它不能被继承。抽象类不能被声明为 sealed。
下面的程序演示了一个抽象类:
using
System
;
namespace
PolymorphismApplication
abstract
class
Shape
abstract
public
int
area
(
)
;
class
Rectangle
:
Shape
private
int
length
;
private
int
width
;
public
Rectangle
(
int
a
=
0
,
int
b
=
0
)
length
=
a
;
width
=
b
;
public
override
int
area
(
)
Console
.
WriteLine
(
"Rectangle 类的面积:"
)
;
return
(
width
*
length
)
;
class
RectangleTester
static
void
Main
(
string
[
]
args
)
Rectangle r
=
new
Rectangle
(
10
,
7
)
;
double
a
=
r
.
area
(
)
;
Console
.
WriteLine
(
"面积: {0}"
,a
)
;
Console
.
ReadKey
(
)
;
当上面的代码被编译和执行时,它会产生下列结果:
Rectangle 类的面积:
面积: 70
当有一个定义在类中的函数需要在继承类中实现时,可以使用
虚方法
。
虚方法是使用关键字
virtual
声明的。
虚方法可以在不同的继承类中有不同的实现。
对虚方法的调用是在运行时发生的。
动态多态性是通过
抽象类
和
虚方法
实现的。
以下实例创建了 Shape 基类,并创建派生类 Circle、 Rectangle、Triangle, Shape 类提供一个名为 Draw 的虚拟方法,在每个派生类中重写该方法以绘制该类的指定形状。
using
System
;
using
System.Collections.Generic
;
public
class
Shape
public
int
X
{
get
;
private
set
;
}
public
int
Y
{
get
;
private
set
;
}
public
int
Height
{
get
;
set
;
}
public
int
Width
{
get
;
set
;
}
// 虚方法
public
virtual
void
Draw
(
)
Console
.
WriteLine
(
"执行基类的画图任务"
)
;
class
Circle
:
Shape
public
override
void
Draw
(
)
Console
.
WriteLine
(
"画一个圆形"
)
;
base
.
Draw
(
)
;
class
Rectangle
:
Shape
public
override
void
Draw
(
)
Console
.
WriteLine
(
"画一个长方形"
)
;
base
.
Draw
(
)
;
class
Triangle
:
Shape
public
override
void
Draw
(
)
Console
.
WriteLine
(
"画一个三角形"
)
;
base
.
Draw
(
)
;
class
Program
static
void
Main
(
string
[
]
args
)
// 创建一个 List<Shape> 对象,并向该对象添加 Circle、Triangle 和 Rectangle
var
shapes
=
new
List
<
Shape
>
new
Rectangle
(
)
,
new
Triangle
(
)
,
new
Circle
(
)
// 使用 foreach 循环对该列表的派生类进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法
foreach
(
var
shape
in
shapes
)
shape
.
Draw
(
)
;
Console
.
WriteLine
(
"按下任意键退出。"
)
;
Console
.
ReadKey
(
)
;
当上面的代码被编译和执行时,它会产生下列结果:
画一个长方形
执行基类的画图任务
画一个三角形
执行基类的画图任务
画一个圆形
执行基类的画图任务
按下任意键退出。
下面的程序演示通过虚方法 area() 来计算不同形状图像的面积:
using
System
;
namespace
PolymorphismApplication
class
Shape
protected
int
width, height
;
public
Shape
(
int
a
=
0
,
int
b
=
0
)
width
=
a
;
height
=
b
;
public
virtual
int
area
(
)
Console
.
WriteLine
(
"父类的面积:"
)
;
return
0
;
class
Rectangle
:
Shape
public
Rectangle
(
int
a
=
0
,
int
b
=
0
)
:
base
(
a, b
)
public
override
int
area
(
)
Console
.
WriteLine
(
"Rectangle 类的面积:"
)
;
return
(
width
*
height
)
;
class
Triangle
:
Shape
public
Triangle
(
int
a
=
0
,
int
b
=
0
)
:
base
(
a, b
)
public
override
int
area
(
)
Console
.
WriteLine
(
"Triangle 类的面积:"
)
;
return
(
width
*
height
/
2
)
;
class
Caller
public
void
CallArea
(
Shape sh
)
int
a
;
a
=
sh
.
area
(
)
;
Console
.
WriteLine
(
"面积: {0}"
, a
)
;
class
Tester
static
void
Main
(
string
[
]
args
)
Caller c
=
new
Caller
(
)
;
Rectangle r
=
new
Rectangle
(
10
,
7
)
;
Triangle t
=
new
Triangle
(
10
,
5
)
;
c
.
CallArea
(
r
)
;
c
.
CallArea
(
t
)
;
Console
.
ReadKey
(
)
;
当上面的代码被编译和执行时,它会产生下列结果:
Rectangle 类的面积:
面积:70
Triangle 类的面积:
面积:25
关键字 abstract 声明抽象类:用于接口部分类的实现(派生类继承抽象类时,实现完成)。抽象类包含抽象方法,抽象方法可被派生类实现。
抽象类规则:
-
1.不能创建抽象类的实例
-
2.不能在抽象类外定义抽象方法
-
3.不能把抽象类声明为sealed(类前带关键字sealed代表该类是密封类,不能被继承)
关键字virtual声明虚方法:用于方法在继承类中的实现(在不同的继承类中有不同的实现)。
抽象类和虚方法共同实现动态多态性。
注:继承类中的重写虚函数需要声明关键字 override,在方法参数传入中写(类名 形参名)例如 public void CallArea(Shape sh),意思是传入一个 shape 类型的类。
2.virtual可以被子类重写,而abstract必须被子类重写。
3.如果类成员被abstract修饰,则该类前必须添加abstract,因为只有抽象类才可以有抽象方法。
4.无法创建abstract类的实例,只能被继承无法实例化。
见:
动态多态性
参数列表必须不相同
返回值类型可以不相同
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();
小提示:经常有童鞋问重载和重写的区别,而且网络上把这两个的区别作为 C# 做常考的面试题之一。实际上这两个概念完全没有关系,仅仅都带有一个“重”字。他们没有在一起比较的意义,仅仅分辨它们不同的定义就好了。
3、虚方法:即为基类中定义的允许在派生类中重写的方法,使用virtual关键字定义。如:
public virtual void EatFood()
Console.WriteLine("Animal吃东西");
注意:虚方法也可以被直接调用。如:
Animal a = new Animal();
a.EatFood();
执行输出结果为:
Animal吃东西
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)和上一条对比:重写方法中子类的变量调用子类重写的方法,父类的变量要看这个父类引用的是子类的实例还是本身的实例,如果引用的是父类的实例那么调用基类的方法,如果引用的是派生类的实例则调用派生类的方法。
更多内容可参考: C# 中的多态性