Использование ключевого слова is
в C # (как вы продемонстрировали выше) почти всегда является запахом кода.И это воняет.
Проблема в том, что теперь требуется что-то, что должно знать только о 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
, и вы будете думать о других особых случаях, которые необходимо обработать, и вы потратите двадцать минут на отслеживание каждого места, где есть 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) // 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
для перечисления, и это позволяет вам (в некоторой степени) узнать конкретный предел того, какие автомобили могут быть использованы.
Недостатком использования enum является то, что CarType
установлен в камне;если другая (внешняя) сборка зависит от ICar
и они добавили новую машину Tesla
, они не смогут добавить тип Tesla
к CarType
.Перечисления также не очень хорошо подходят для иерархий классов: если вы хотите, чтобы Chevy
были CarType.Chevy
и a CarType.GM
, вы должны либо использовать перечисление в качестве флагов (уродливо в этомили убедитесь, что вы проверили Chevy
до GM
или у вас есть много ||
в ваших чеках против перечислений.