多态性

  继承的一个结果是派生于基类的类在方法和属性上有一定的重叠,因此,可以使用相同的语法处理从同一个基类实例化的对象。例如,如果基类 Animal 有一个 EatFood() 方法,则在其派生类 CowChicken 中调用这个方法的语法是类似的:

  1. Cow myCow = new Cow();
  2. Chicken myChicken = new Chicken();
  3. myCow.EatFood();
  4. myChicken.EatFood();

  多态性则更推进了一步。可以把某个派生类型的变量赋给基本类型的变量,例如:

  1. Animal myAnimal = myCow;

  不需要进行强制类型转换,就可以通过这个变量调用基类的方法:

  1. myAnimal.EatFood();

  结果是调用派生类中的 EatFood() 的实现代码。注意 ⚠️,不能以相同的方式调用派生类上定义的方法。下面的代码不能运行:

  1. myAnimal.Moo();

  但可以把基本类型的变量转换为派生类变量,调用派生类的方法,如下所示:

  1. Cow myNewCow = (Cow)myAnimal;
  2. myNewCow.Moo();

  如果原始变量的类型不是 Cow 或派生于 Cow 的类型,这个强制类型转换就会引发一个异常。有许多方式说明对象的类型是什么,详见下一章。

  在派生于同一个类的不同对象上执行任务时,多态性是一种极有效的技巧,其使用的代码最少。注意 ⚠️并不是只有共享同一个父类的类才能利用多态性。只要子类和孙子类在继承层次结构中有一个相同的类,它们就可以用同样的方式利用多态性。

  还要注意,在C#中,所有类都派生于同一个类 objectobject 是继承层次结构中的根。所以可以把所有对象看成 object 类的实例。这就是在建立字符串时,Console.WriteLine() 可以处理无数多种参数组合的原因。第一个参数后面的每个参数都可以看成一个 object 实例,所以可以把人和对象的输出结果写到屏幕上。为此,需要调用 ToString()object 的一个成员)。我们可以重写这个方法,为自己的类提供合适的实现代码,或者使用默认实现代码,返回类名(根据它所在的名称空间,返回类的限定名称)。

  接口的多态性

  尽管不能像对象那样实例化接口,但可以建立接口类型的变量,然后就可以在支持该接口的对象上,使用这个变量来访问该接口提供的方法和属性。

  例如,假定不使用基类 Animal 提供 EatFood() 方法,而是把该方法放在 IConsume 接口上。CowChicken 类也支持这个接口,唯一的区别是它们必须提供 EatFood() 方法的实现代码(因为接口不包含实现代码),接着就可以使用下述代码访问该方法了:

  1. Cow myCow = new Cow();
  2. Chicken myChicken = new Chicken();
  3. IConsume consumeInterface;
  4. consumeInterface = myCow;
  5. consumeInterface.EatFood();
  6. consumeInterface = myChicken;
  7. consumeInterface.EatFood();

  这就提供了以相同方式访问多个对象的简单方式,且不依赖于一个公共的基类。例如,这个接口可以由派生于 Vegetable 而不是 AnimalVenusFlyTrap 类实现:

  1. VenusFlyTrap myVenusFlyTrap = new VenusFlyTrip();
  2. IConsum consumeInterface;
  3. consumeInterface = myVenusFlyTrip;
  4. consumeInterface.EatFood();

  在这段代码中,调用 consumeInterface.EatFood() 的结果是调用 CowChickenVenusFlyTrap 类的 EatFood() 方法,这取决于把哪个实例赋予接口类型的变量。

  注意 ⚠️,派生类会继承其基类支持的接口。在上面的第一个示例中,要么是 Animal 支持 IConsume,要么是 CowChicken 支持 IConsume。有共同基类的类不一定有共同的接口,反之亦凡。