在C#中使用is
关键字(如上所示)几乎总是存在代码异味,而且很难闻。
问题在于,本应只知道一个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();
}
}
}
并且接下来,另一辆车在你踩下油门时将会执行一些特殊操作。你将把这个功能添加到
Driver
中,还要考虑其他需要处理的特殊情况,并浪费二十分钟跟踪代码库中到处都是的
if(car is Foo)
,因为它现在分散在
Driver
、
Garage
和
ParkingLot
中。(我在处理遗留代码时得出了这样的经验)。
当你发现自己像这样写
if (instance is SomeObject)
语句时,停下来问问自己为什么需要在这里处理这个特殊行为。大部分情况下,这可以成为接口/抽象类中的一个新方法,并且你可以为那些不是“特殊”的类提供默认实现。
这并不是说你绝对不应该使用
is
检查类型;然而,你必须非常小心地进行这个操作,因为它很容易失控并被滥用,除非加以限制。
现在,假设你已经确定必须对你的
ICar
进行类型检查。使用
is
的问题在于,在你进行两次转换时,静态代码分析工具会向你发出警告。
if (car is Bmw)
{
((Bmw)car).ShiftLanesWithoutATurnSignal();
}
除非这个代码块在内部循环中,否则性能影响可能是可以忽略不计的。但是首选的编写方式是:
var bmw = car as Bmw;
if (bmw != null)
{
bmw.ParkInThreeSpotsAtOnce();
}
这只需要一个转换(内部)而不是两个。
如果你不想走那条路,另一个干净的方法是简单地使用枚举:
enum CarType
{
Bmw,
Toyota,
Kia
}
interface ICar
{
void Go();
CarType Make
{
get;
}
}
紧随其后
if (car.Make == CarType.Kia)
{
((Kia)car).TalkOnCellPhoneAndGoFifteenUnderSpeedLimit();
}
你可以快速地使用枚举进行开关
,并且它让您知道(在某种程度上)可以使用哪些汽车。
使用枚举的一个缺点是,CarType
是固定的; 如果另一个(外部)程序集依赖于ICar
并添加了新的Tesla
汽车,则不能将Tesla
类型添加到CarType
中。 枚举也不适用于类层次结构:如果您希望Chevy
成为CarType.Chevy
和 CarType.GM
,则必须将枚举用作标志(在这种情况下难看),或者确保在检查枚举时先检查Chevy
,再检查GM
,或者在对枚举进行检查时使用大量的||
操作符。