在 C# 中使用is
关键字(以您上面演示的方式)几乎总是代码异味。而且很臭。
问题是,现在需要一些应该只知道 an 的东西ICar
来跟踪实现的几个不同的类ICar
。虽然这有效(因为它产生了运行的代码),但它的设计很糟糕。你将从几辆车开始...
class Driver
{
private ICar car = GetCarFromGarage();
public void FloorIt()
{
if (this.car is Bmw)
{
((Bmw)this.car).AccelerateReallyFast();
}
else if (this.car is Toyota)
{
((Toyota)this.car).StickAccelerator();
}
else
{
this.car.Go();
}
}
}
稍后,另一辆车会在你做一些特别的事情FloorIt
。然后你将把这个特性添加到 中Driver
,你会考虑其他需要处理的特殊情况,你会浪费 20 分钟来追踪每个有 的地方if (car is Foo)
,因为它现在分散在整个代码库中-- inside Driver
, inside Garage
, inside ParkingLot
... (我是根据处理遗留代码的经验来说话的。)
当你发现自己在做出类似的声明if (instance is SomeObject)
时,停下来问问自己为什么需要在这里处理这种特殊行为。大多数时候,它可以是接口/抽象类中的新方法,您可以简单地为非“特殊”类提供默认实现。
这并不是说您绝对不应该使用is
;检查类型。但是,在这种做法中您必须非常小心,因为除非得到控制,否则它往往会失控并被滥用。
现在,假设您已经确定必须对您的ICar
. 使用的问题is
是静态代码分析工具会警告您两次转换,当您这样做时
if (car is Bmw)
{
((Bmw)car).ShiftLanesWithoutATurnSignal();
}
除非它在内部循环中,否则性能影响可能可以忽略不计,但编写它的首选方式是
var bmw = car as Bmw;
if (bmw != null) // careful about overloaded == here
{
bmw.ParkInThreeSpotsAtOnce();
}
这只需要一个演员(内部)而不是两个。
如果您不想走那条路,另一种干净的方法是简单地使用枚举:
enum CarType
{
Bmw,
Toyota,
Kia
}
interface ICar
{
void Go();
CarType Make
{
get;
}
}
其次是
if (car.Make == CarType.Kia)
{
((Kia)car).TalkOnCellPhoneAndGoFifteenUnderSpeedLimit();
}
您可以快速switch
进行枚举,它可以让您(在某种程度上)知道可能使用哪些汽车的具体限制。
使用枚举的一个缺点是一成不变的CarType
。如果另一个(外部)组件依赖于ICar
并且他们添加了Tesla
新车,他们将无法将Tesla
类型添加到CarType
. 枚举也不适合类层次结构:如果您希望 aChevy
成为 aCarType.Chevy
和a CarType.GM
,则必须将枚举用作标志(在这种情况下很难看)或确保检查Chevy
beforeGM
或有很多||
s在您对枚举的检查中。