多态性(C# 编程指南)
C# 编程指南
通过继承,一个类可以用作多种类型:可以用作它自己的类型、任何基类型,或者在实现接口时用作任何接口类型。这称为多态性。C# 中的每种类型都是多态的。类型可用作它们自己的类型或用作 Object 实例,因为任何类型都自动将 Object 当作基类型。
多态性不仅对派生类很重要,对基类也很重要。任何情况下,使用基类实际上都可能是在使用已强制转换为基类类型的派生类对象。基类的设计者可以预测到其基类中可能会在派生类中发生更改的方面。例如,表示汽车的基类可能包含这样的行为:当考虑的汽车为小型货车或敞篷汽车时,这些行为将会改变。基类可以将这些类成员标记为虚拟的,从而允许表示敞篷汽车和小型货车的派生类重写该行为。
有关更多信息,请参见继承。
多态性概述
当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。若要更改基类的数据和行为,您有两种选择:可以使用新的派生成员替换基成员,或者可以重写虚拟的基成员。
使用新的派生成员替换基类的成员需要使用 new 关键字。如果基类定义了一个方法、字段或属性,则 new 关键字用于在派生类中创建该方法、字段或属性的新定义。new 关键字放置在要替换的类成员的返回类型之前。例如:
public class BaseClass { public void DoWork() { } public int WorkField; public int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass { public new void DoWork() { } public new int WorkField; public new int WorkProperty { get { return 0; } } }
使用 new 关键字时,调用的是新的类成员而不是已被替换的基类成员。这些基类成员称为隐藏成员。如果将派生类的实例强制转换为基类的实例,就仍然可以调用隐藏类成员。例如:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
为了使派生类的实例完全接替来自基类的类成员,基类必须将该成员声明为虚拟的。这是通过在该成员的返回类型之前添加 virtual 关键字来实现的。然后,派生类可以选择使用 override 关键字而不是 new,将基类实现替换为它自己的实现。例如:
public class BaseClass { public virtual void DoWork() { } public virtual int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass { public override void DoWork() { } public override int WorkProperty { get { return 0; } } }
字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。例如:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.
使用虚拟方法和属性可以预先计划未来的扩展。由于在调用虚拟成员时不考虑调用方正在使用的类型,所以派生类可以选择完全更改基类的外观行为。
无论在派生类和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员都将永远为虚拟成员。如果类 A 声明了一个虚拟成员,类 B 从 A 派生,类 C 从类 B 派生,则类 C 继承该虚拟成员,并且可以选择重写它,而不管类 B 是否为该成员声明了重写。例如:
public class A { public virtual void DoWork() { } } public class B : A { public override void DoWork() { } }
public class C : B { public override void DoWork() { } }
派生类可以通过将重写声明为密封的来停止虚拟继承。这需要在类成员声明中将 sealed 关键字放在 override 关键字的前面。例如:
public class C : B { public sealed override void DoWork() { } }
在上面的示例中,方法 DoWork 对从 C 派生的任何类都不再是虚拟的。它对 C 的实例仍然是虚拟的 -- 即使将这些实例强制转换为类型 B 或类型 A。派生类可以通过使用 new 关键字替换密封的方法,如下面的示例所示:
public class D : C { public new void DoWork() { } }
在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 DoWork。如果使用类型为 C、B 或 A 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 C 的 DoWork 实现。
已替换或重写某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性。例如:
public class A { public virtual void DoWork() { } } public class B : A { public override void DoWork() { } }
public class C : B { public override void DoWork() { // Call DoWork on B to get B's behavior: base.DoWork(); // DoWork behavior specific to C goes here: // ... } }
有关更多信息,请参见 base。
注意 |
---|
建议虚拟成员在它们自己的实现中使用 base 来调用该成员的基类实现。允许基类行为发生使得派生类能够集中精力实现特定于派生类的行为。未调用基类实现时,由派生类负责使它们的行为与基类的行为兼容。 |
-----------------------------------------------------------
C# 允许派生类中的方法与基类中的方法具有相同的名称,只要您非常明确应如何处理新方法。下面的示例演示 new 和 override 关键字的使用。
首先声明三个类:一个名为 Car 的基类以及从该基类派生的两个类 ConvertibleCar 和 Minivan。基类包含一个可将有关汽车的描述发送到控制台的方法 (DescribeCar)。派生类方法也包含一个名为 DescribeCar 的方法,该方法显示派生类的独特属性。这些方法还调用基类的 DescribeCar 方法来演示从 Car 类继承属性的方式。
为了强调区别,ConvertibleCar 类使用 new 关键字来定义,而 Minivan 类使用 override 来定义。
// Define the base class class Car { public virtual void DescribeCar() { System.Console.WriteLine("Four wheels and an engine."); } } // Define the derived classes class ConvertibleCar : Car { public new virtual void DescribeCar() { base.DescribeCar(); System.Console.WriteLine("A roof that opens up."); } } class Minivan : Car { public override void DescribeCar() { base.DescribeCar(); System.Console.WriteLine("Carries seven people."); } }
现在可以编写一些代码来声明这些类的实例,并调用它们的方法以便对象能够描述其自身:
public static void TestCars1() { Car car1 = new Car(); car1.DescribeCar(); System.Console.WriteLine("----------"); ConvertibleCar car2 = new ConvertibleCar(); car2.DescribeCar(); System.Console.WriteLine("----------"); Minivan car3 = new Minivan(); car3.DescribeCar(); System.Console.WriteLine("----------"); }
正如预期的那样,输出类似如下所示:
Four wheels and an engine.
----------
Four wheels and an engine.
A roof that opens up.
----------
Four wheels and an engine.
Carries seven people.
----------
但是,在该代码接下来的一节中,我们声明了一个从 Car 基类派生的对象的数组。此数组能够存储 Car、ConvertibleCar 和 Minivan 对象。该数组的声明类似如下所示:
public static void TestCars2() { Car[] cars = new Car[3]; cars[0] = new Car(); cars[1] = new ConvertibleCar(); cars[2] = new Minivan(); }
然后可以使用一个 foreach 循环来访问该数组中包含的每个 Car 对象,并调用 DescribeCar 方法,如下所示:
foreach (Car vehicle in cars) { System.Console.WriteLine("Car object: " + vehicle.GetType()); vehicle.DescribeCar(); System.Console.WriteLine("----------"); }
此循环的输出如下所示:
Car object: YourApplication.Car
Four wheels and an engine.
----------
Car object: YourApplication.ConvertibleCar
Four wheels and an engine.
----------
Car object: YourApplication.Minivan
Four wheels and an engine.
Carries seven people.
----------
注意,ConvertibleCar 说明与您的预期不同。由于使用了 new 关键字来定义此方法,所调用的不是派生类方法,而是基类方法。Minivan 对象正确地调用重写方法,并产生预期的结果。
若要强制一个规则,要求从 Car 派生的所有类都必须实现 DescribeCar 方法,则应创建一个新的基类,将方法 DescribeCar 定义为 abstract。抽象方法不包含任何代码,仅包含方法签名。从此基类派生的任何类都必须提供 DescribeCar 的实现。有关更多信息,请参见抽象。
----------------------------------------
http://msdn2.microsoft.com/zh-cn/library/ms173153(VS.80).aspx