Видя, что C # не может switch
использоваться для типа (который, как я понял, не был добавлен в качестве особого случая, потому что is
отношения означают, что case
может применяться более одного отдельного элемента), есть ли лучший способ имитировать переключение на тип, отличный от этого?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
switch
оператор. Один пример: сборка A содержит набор объектов данных (которые не будут изменены, определенные в документе спецификации или тому подобное). Сборки B , C и D каждая ссылаются на A и обеспечивают преобразование для различных объектов данных из A (например, сериализация / десериализация в некоторый конкретный формат). Вы либо должны отразить всю иерархию классов в B , C и D и использовать фабрики, либо у вас есть ...Ответы:
Переключение типов определенно отсутствует в C # ( ОБНОВЛЕНИЕ: в C # 7 / VS 2017 поддерживается переключение типов - см. Ответ Захария Йейтса ниже ). Чтобы сделать это без большого оператора if / else if / else, вам нужно работать с другой структурой. Некоторое время назад я написал сообщение в блоге, в котором подробно рассказывалось, как построить структуру TypeSwitch.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Короткая версия: TypeSwitch предназначен для предотвращения избыточного приведения и предоставления синтаксиса, который похож на обычный оператор switch / case. Например, вот TypeSwitch в действии со стандартным событием формы Windows
Код для TypeSwitch на самом деле довольно маленький и может быть легко вставлен в ваш проект.
источник
foreach
(что будет происходить, только если совпадение не найдено)CaseInfo
, просто проверяя значение типа (если оно равно null, это значение по умолчанию).В C # 7 , поставляемом с Visual Studio 2017 (выпуск 15. *), вы можете использовать типы в
case
выражениях (сопоставление с образцом):В C # 6 вы можете использовать оператор switch с оператором nameof () (спасибо @Joey Adams):
В C # 5 и более ранних версиях вы можете использовать оператор switch, но вам придется использовать магическую строку, содержащую имя типа ... которая не особенно удобна для рефакторинга (спасибо @nukefusion)
источник
nameof()
оператор .case UnauthorizedException _:
Одним из вариантов является наличие словаря от
Type
доAction
(или некоторого другого делегата). Найдите действие, основанное на типе, и затем выполните его. Я использовал это для заводов до сих пор.источник
С ответом JaredPar в затылке я написал вариант его
TypeSwitch
класса, который использует вывод типов для лучшего синтаксиса:Обратите внимание, что порядок
Case()
методов важен.Получить полный и прокомментированный код для моего
TypeSwitch
класса . Это рабочая сокращенная версия:источник
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. Это позволяет вам сказатьvalue.Case((C x) ...
Создайте суперкласс (S) и сделайте так, чтобы A и B наследовали его. Затем объявите абстрактный метод на S, который должен реализовать каждый подкласс.
Делая это, метод "foo" также может изменить свою сигнатуру на Foo (S o), что делает его безопасным, и вам не нужно бросать это безобразное исключение.
источник
Вы можете использовать сопоставление с образцом в C # 7 или выше:
источник
Вы действительно должны перегружать свой метод, а не пытаться сделать устранение неоднозначности самостоятельно. Большинство ответов до сих пор не учитывают будущие подклассы, что в дальнейшем может привести к действительно ужасным проблемам обслуживания.
источник
Если бы вы использовали C # 4, вы могли бы использовать новую динамическую функциональность для создания интересной альтернативы. Я не говорю, что это лучше, на самом деле кажется, что это будет медленнее, но в нем есть определенная элегантность.
И использование:
Причина, по которой это работает, заключается в том, что при вызове динамического метода C # 4 перегрузки разрешаются во время выполнения, а не во время компиляции. Я написал немного больше об этой идее совсем недавно . Опять же, я просто хотел бы повторить, что это, вероятно, работает хуже, чем все другие предложения, я предлагаю это просто из любопытства.
источник
Да, благодаря C # 7 этого можно достичь. Вот как это делается (с использованием шаблона выражения ):
источник
Для встроенных типов вы можете использовать перечисление TypeCode. Обратите внимание, что GetType () довольно медленный, но, вероятно, не подходит в большинстве ситуаций.
Для пользовательских типов вы можете создать собственное перечисление, а также интерфейс или базовый класс с абстрактным свойством или методом ...
Абстрактный класс реализации свойства
Реализация класса абстрактного метода
Интерфейсная реализация свойства
Интерфейсная реализация метода
Один из моих коллег только что рассказал мне об этом: у этого есть то преимущество, что вы можете использовать его буквально для любого типа объекта, а не только для того, который вы определяете. Недостатком является то, что он немного больше и медленнее.
Сначала определите статический класс следующим образом:
И тогда вы можете использовать это так:
источник
Мне понравилось использование Virtlink неявной типизации, чтобы сделать переключение намного более читабельным, но мне не понравилось, что ранний выход невозможен, и что мы делаем распределение. Давайте немного прибавим.
Ну, это заставляет мои пальцы болеть. Давайте сделаем это в T4:
Настроим пример Virtlink немного:
Читается и быстро. Теперь, когда все продолжают указывать в своих ответах и учитывая природу этого вопроса, порядок соответствия важен при сопоставлении типов. Следовательно:
источник
Учитывая, что наследование облегчает распознавание объекта как более одного типа, я думаю, что переключение может привести к плохой неопределенности. Например:
Случай 1
Дело 2
Потому что s это строка и объект. Я думаю, что когда вы пишете,
switch(foo)
вы ожидаете, что foo будет соответствовать одному и только одномуcase
утверждению. С переключением типов порядок, в котором вы пишете операторы case, может изменить результат всего оператора switch. Я думаю, что это было бы неправильно.Можно подумать о проверке компилятором типов оператора «typeswitch», проверяющего, что перечисленные типы не наследуются друг от друга. Этого не существует, хотя.
foo is T
это не то же самое, чтоfoo.GetType() == typeof(T)
!!источник
Я бы либо
источник
Другой способ - определить интерфейс IThing, а затем реализовать его в обоих классах. Вот фрагмент кода:
источник
Согласно спецификации C # 7.0, вы можете объявить локальную переменную в
case
областиswitch
:Это лучший способ сделать это, потому что он включает в себя только операции приведения и операции над стеком, которые являются самыми быстрыми операциями, которые интерпретатор может выполнить сразу после побитовых операций и
boolean
условий.Сравнивая это с a
Dictionary<K, V>
, мы получаем гораздо меньшее использование памяти: для хранения словаря требуется больше места в оперативной памяти, а процессор требует больше вычислений для создания двух массивов (один для ключей и другой для значений) и сбора хэш-кодов для ключей, которые нужно поместить значения их соответствующих ключей.Итак, насколько я знаю, я не верю, что более быстрый способ мог бы существовать, если вы не хотите использовать только
if
-then
-else
блок сis
оператором следующим образом:источник
Вы можете создавать перегруженные методы:
И приведите аргумент к
dynamic
типу, чтобы обойти статическую проверку типов:источник
C # 8 улучшений сопоставления с образцом позволили сделать это следующим образом. В некоторых случаях это делает работу и более кратким.
источник
Вы ищете
Discriminated Unions
какую-либо языковую особенность F #, но вы можете добиться аналогичного эффекта, используя созданную мной библиотеку под названием OneOfhttps://github.com/mcintyre321/OneOf
Основное преимущество перед
switch
(иif
иexceptions as control flow
) заключается в том, что он безопасен во время компиляции - здесь нет обработчика по умолчанию или сбойЕсли вы добавите третий элемент в o, вы получите ошибку компилятора, так как вам нужно добавить обработчик Func внутри вызова switch.
Вы также можете сделать,
.Match
который возвращает значение, а не выполняет инструкцию:источник
Создайте интерфейс
IFooable
, затем создайте свой классA
иB
класс для реализации общего метода, который, в свою очередь, вызывает соответствующий метод, который вы хотите:Обратите внимание, что лучше использовать
as
вместо этого сначала проверку с помощьюis
приведения, а затем приведение, так как вы делаете 2 приведения, так что это дороже.источник
В таких случаях я обычно получаю список предикатов и действий. Что-то в этом роде:
источник
После сравнения вариантов нескольких ответов, представленных здесь, с возможностями F #, я обнаружил, что в F # улучшена поддержка переключения на основе типов (хотя я все еще придерживаюсь C #).
Возможно, вы захотите увидеть здесь и здесь .
источник
Я хотел бы создать интерфейс с любым именем и именем метода, которое имело бы смысл для вашего коммутатора, давайте назовем их соответственно:
IDoable
это говорит о необходимости реализацииvoid Do()
.и измените метод следующим образом:
По крайней мере, с этим вы в безопасности во время компиляции, и я подозреваю, что с точки зрения производительности это лучше, чем проверка типа во время выполнения.
источник
С C # 8 вы можете сделать его еще более лаконичным с новым коммутатором. А с помощью опции discard _ вы можете избежать создания бесполезных переменных, когда они вам не нужны, например:
Invoice и ShippingList являются классами, а document является объектом, который может быть любым из них.
источник
Я согласен с Джоном по поводу наличия хеша действий для имени класса. Если вы сохраняете свой шаблон, вы можете использовать вместо этого конструкцию «as»:
Разница в том, что когда вы используете шаблон, if (foo is Bar) {((Bar) foo) .Action (); } вы делаете приведение типов дважды. Теперь, может быть, компилятор оптимизирует и выполнит эту работу только один раз - но я бы на это не рассчитывал.
источник
Как полагает Пабло, интерфейсный подход - почти всегда правильная вещь, чтобы справиться с этим. Чтобы по-настоящему использовать switch, другой альтернативой является создание собственного перечисления, обозначающего ваш тип в ваших классах.
Это также реализовано в BCL. Один пример - MemberInfo.MemberTypes , другой -
GetTypeCode
для примитивных типов, например:источник
Это альтернативный ответ, который сочетает вклады ответов JaredPar и VirtLink со следующими ограничениями:
Использование:
Код:
источник
Да - просто используйте слегка странное название «сопоставление с образцом» из C # 7 и выше, чтобы сопоставить класс или структуру:
источник
я использую
источник
Должен работать с
лайк:
источник
Если вы знаете ожидаемый класс, но у вас все еще нет объекта, вы даже можете сделать это:
источник