在基类和派生类中实现接口
本文关键字:实现 接口 派生 基类 | 更新日期: 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 : Cat
和Cat : IMakeNoise
,那么通过传递性,Lion : IMakeNoise
在Lion
上自动声明是多余和不必要的。这是因为如果不继承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。
面向接口的编程还使您可以更轻松地接受控制反转/依赖注入。