在基类和派生类中实现接口

本文关键字:实现 接口 派生 基类 | 更新日期: 2023-09-27 18:10:30

在c#中,你能举一个很好的例子来说明为什么你要在基类上实现一个接口,然后在派生类上重新实现该接口,而不是使基类方法成为虚的吗?

例如:

interface IMakesNoise
{
  void Speak();
}
class Cat : IMakesNoise
{
  public void Speak()
  {
    Console.WriteLine("MEOW");
  }
}
class Lion : Cat, IMakesNoise
{
  public new void Speak()
  {
    Console.WriteLine("ROAR");
  }
}

测试行为:

Cat cat = new Cat();
Cat lion = new Lion();
// Non virtual calls, acts as expected    
cat.Speak();
lion.Speak();
// Grabbing the interface out is 'virtual' in that it grabs the most derived interface implementation
(cat as IMakesNoise).Speak();
(lion as IMakesNoise).Speak();

这将打印出:

MEOW
MEOW
MEOW
ROAR

更新:关于为什么的更多澄清,原因是我正在实现一个编译器,我想知道c#选择这种接口实现的原因。

在基类和派生类中实现接口

我看过这个问题。

c#中ComVisible类的接口继承

c#暴露于COM接口继承

据我所知,如果你有两个对象,并希望它们通过COM可见,它们都应该显式地继承所需的接口。

如果Lion : CatCat : IMakeNoise,那么通过传递性,Lion : IMakeNoiseLion上自动声明是多余和不必要的。这是因为如果不继承Cat的所有属性(包括接口),Lion就不能成为Cat

虚方法的存在使您不仅可以重写,而且可以完全替换派生层次结构中的功能。换句话说,您可以在派生程度较高的类中更改派生程度较低的类的功能。这与隐藏的不同之处在于,隐藏只覆盖给定派生类的方法功能,而不是覆盖整个类层次结构。

遮蔽是通过声明存在于基类中的相同的非虚方法来实现的,同时添加new关键字来表明有意进行遮蔽行为。

重写是通过声明与基类中存在的相同的virtual方法来实现的,同时添加override关键字来指示重写行为是有意的。

代码示例将使这些差异非常清楚。让我们定义一个基本的Vehicle类(抽象的,所以不能被实例化)和一个派生的Motorcycle类。两者都将向控制台输出关于它们所拥有的车轮数量的信息:

/// <summary>
/// Represents a Vehicle.
/// </summary>
public abstract class Vehicle
{
    /// <summary>
    /// Prints the Number of Wheels to the Console.
    /// Virtual so can be changed by more derived types.
    /// </summary>
    public virtual void VirtualPrintNumberOfWheels()
    {
        Console.WriteLine("Number of Wheels: 4");
    }
    /// <summary>
    /// Prints the Number of Wheels to the Console.
    /// </summary>
    public void ShadowPrintNumberOfWheels()
    {
        Console.WriteLine("Number of Wheels: 4");
    }
}
/// <summary>
/// Represents a Motorcycle.
/// </summary>
public class Motorcycle : Vehicle
{
    /// <summary>
    /// Prints the Number of Wheels to the Console.
    /// Overrides base method.
    /// </summary>
    public override void VirtualPrintNumberOfWheels()
    {
        Console.WriteLine("Number of Wheels: 2");
    }
    /// <summary>
    /// Prints the Number of Wheels to the Console.
    /// Shadows base method.
    /// </summary>
    public new void ShadowPrintNumberOfWheels()
    {
        Console.WriteLine("Number of Wheels: 2");
    }
}

上面我们定义了两个类:一个抽象的基本Vehicle类,它有一个做同样事情的虚方法和非虚方法;一个Motorcycle类,它实现了Vehicle类,同时覆盖了虚方法并遮蔽了普通方法。现在我们将使用不同的Type签名调用方法来查看差异:

static void Main(string[] args)
{
    // Instantiate a Motorcycle as type Motorcycle
    Motorcycle vehicle = new Motorcycle();
    vehicle.ShadowPrintNumberOfWheels();
    vehicle.VirtualPrintNumberOfWheels();
    // Instantiate a Motorcycle as type Vehicle
    Vehicle otherVehicle = new Motorcycle();
    // Calling Shadow on Motorcycle as Type Vehicle
    otherVehicle.ShadowPrintNumberOfWheels();
    otherVehicle.VirtualPrintNumberOfWheels();
    Console.ReadKey();
}
结果:

Number of Wheels: 2
Number of Wheels: 2
Number of Wheels: 4
Number of Wheels: 2

上面的代码示例有几个问题,但我将把它们放在一边,只关注最重要的问题:它违反了面向接口而不是面向实现编程的原则。如果将cat和lion实例的声明更改为以下内容,您看到的问题就会消失。

IMakesNoise cat = new Cat();
IMakesNoise lion = new Lion();

在这种情况下,您的输出是预期的MEOW, ROAR, MEOW, ROAR。

面向接口的编程还使您可以更轻松地接受控制反转/依赖注入。