第11章 多态

在理解C#多态性之前,首先理解一下什么是多态。多态是面向对象编程的重要概念,是面向对象程序设计(OOP[1])中的三大机制之一。因为有了继承,一个类可以从类继承,可以实现接口,那么这个类除了有它本身的类型,还将具有基类的类型,以及它所实现的接口的类型,也就意味着一个基类的引用可以指向它的所有派生类型,那么当通过基类的引用调用在派生类中实现的方法时,不同的派生类将产生不同的调用结果,这就是多态性。可见继承机制是实现多态的基础。C#中的每种类型都是多态的,因为任何类型都从Object类型派生。

举个简单的例子说明,假如有一个数组,其中有3个元素B、C、D,它们都有共同的基类A,在基类中有一个方法M,这些派生类也都有一个方法M的实现,当我们调用这3个元素的M方法时,将产生3种不同的结果,这就是多态,如图11-1所示。

第11章 多态 - 图1

图 11-1 多态

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

❑编译时的多态性:编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数个数、参数类型等信息决定使用哪个重载方法,以实现某种操作。

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

下面我们来分别理解一下C#多态中涉及的几个概念:重载方法、虚方法、覆写方法和抽象类及抽象方法。

11.1 重载方法

在8.9节我们已经介绍过方法的重载,本章将进一步学习这一重要概念。我们知道,重载就是在同一个类中存在多个同名的方法,但签名却不同。因此,方法的重载需要以下几个前提条件:

❑在同一个类中;

❑方法名相同;

❑方法签名不同。

需要特别注意的是方法签名,方法的签名包括方法的名称以及参数信息(包括形参的修饰符、数目、类型以及泛型参数的数目)。特别需要注意的是,返回值类型、形参和类型参数的名称并不属于方法签名的一部分。

重载是编译时的多态,事实上是一种静态绑定。

以上是针对非泛型方法的重载,而对于自.NET 2.0便开始引入的泛型技术[2],泛型类的方法重载如代码清单11-1所示:

代码清单11-1 泛型类的方法重载


1 using System;

2 public class MethodSample

3{

4 public static void Main()

5{

6 TestClass<int,string>tc=new TestClass<int,string>();

7 tc.DoSomething(5,"10");

8 tc.DoSomething("10",5);

9

10 TestClass<int,int>tc2=new TestClass<int,int>();

11 tc2.DoSomething(5,10);

12 tc2.DoSomething(10,5);

13}

14}

15

16 public class TestClass<T,V>

17{

18 public T DoSomething(T n,V m)//泛型方法

19{

20 Console.WriteLine(“方法1”);

21 return n;

22}

23

24 public T DoSomething(V n,T m)//泛型方法

25{

26 Console.WriteLine(“方法2”);

27 return m;

28}

29

30 public int DoSomething(int m,int n)//非泛型方法

31{

32 Console.WriteLine(“方法3”);

33 return 0;

34}

35}


上述代码定义了一个具有两个泛型类型参数(T和V)的泛型类TestClass,其中定义了两个泛型方法和一个非泛型方法。

先来观察下第6行至第8行,分别使用int和string作为类型参数实例化TestClass类,那么,此时两个泛型方法其实为代码清单11-2所示。

代码清单11-2 使用int和string作为类型参数实例化TestClass类


public int DoSomething(int n,string m)

{

Console.WriteLine(“方法1”);

return n;

}

public int DoSomething(string n,int m)

{

Console.WriteLine(“方法2”);

return m;

}


这样,即演变成非泛型方法的重载,如图11-2所示。

第11章 多态 - 图2

图 11-2 使用int、string作为类型参数实例化TestClass类

但是,如果像第10~12行那样又如何呢?使用两个int作为类型参数实例化TestClass类,此时,TestClass类见代码清单11-3和图11-3。

代码清单11-3 使用两个int作为类型参数实例化TestClass类


public class TestClass<int,int>

{

public int DoSomething(int n,int m)

{

Console.WriteLine(“方法1”);

return n;

}

public int DoSomething(int n,int m)

{

Console.WriteLine(“方法2”);

return m;

}

public int DoSomething(int m,int n)

{

Console.WriteLine(“方法3”);

return 0;

}

}


第11章 多态 - 图3

图 11-3 T、V均使用int作为类型参数

根据重载的定义,这三个方法的方法签名完全一样,是否造成了冲突呢?编译一下代码清单11-1,结果一切正常,CLR并没有如预期般抛出异常,这是怎么回事呢?这是因为有第3个非泛型方法存在(代码清单11-1的第30行),如果删掉第3个非泛型重载方法,或者使用两个string作为类型参数,如图11-4所示,系统将抛出如下异常:

在以下方法或属性之间的调用不明确:


“ProgrammingCSharp4.TestClass<T,V>.DoSomething(T,V)”和

“ProgrammingCSharp4.TestClass<T,V>.DoSomething(V,T)”


即,在两个方法之间发生混淆,因为这两个方法的签名相同,运行时无法在第一个和第二个之间做出选择。我们由此可知:

❑当非泛型方法和泛型方法具有相同的签名时,运行时将优先调用非泛型方法;

❑泛型方法的重载是否产生混淆,是在运行时调用的时候检查的,而非编译的时候检查。

第11章 多态 - 图4

图 11-4 泛型方法重载中发生的混淆

[1]OOP:Object Oriented Programming,面向对象的程序设计。

[2]泛型将在第17章介绍,请读者自行参阅。