Есть ли лучшая альтернатива, чем эта, чтобы «включить тип»?

Видя, что 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());
    }
}
18.11.2008 15:04:04
Из любопытства, почему вы просто не используете полиморфизм?
user1342582 30.07.2009 09:36:52
@jeyoung запечатал классы, и это не стоит для специальных ситуаций
xyz 22.02.2011 21:12:06
@jeyoung: типичная ситуация, когда полиморфизм не может быть использован, - это когда переключаемые типы не должны знать код, содержащий switchоператор. Один пример: сборка A содержит набор объектов данных (которые не будут изменены, определенные в документе спецификации или тому подобное). Сборки B , C и D каждая ссылаются на A и обеспечивают преобразование для различных объектов данных из A (например, сериализация / десериализация в некоторый конкретный формат). Вам либо нужно отразить всю иерархию классов в B , C и D , и использовать фабрики, либо у вас есть ...
O. R. Mapper 26.04.2015 12:21:20
30 ОТВЕТОВ
РЕШЕНИЕ

Переключение типов определенно отсутствует в 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.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

Код для TypeSwitch на самом деле довольно маленький и может быть легко вставлен в ваш проект.

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}
273
23.02.2020 04:25:33
«type == entry.Target» также можно изменить на «entry.Target.IsAssignableFrom (type)», чтобы учитывать совместимые типы (например, подклассы).
Mark Cidade 18.11.2008 16:46:55
Изменен код для использования «entry.Target.IsAssignableFrom (type)», чтобы подклассы поддерживали.
Matt Howells 6.02.2012 15:31:52
Стоит отметить, что (из того, что я понимаю) необходимо указать последнее действие «по умолчанию», чтобы обеспечить проверку всех остальных случаев. Я полагаю, что это не является обязательным требованием для стандартного коммутатора - не то, чтобы я когда-либо видел, чтобы кто-нибудь пытался установить «дефолт» где-либо, кроме дна. Пару отказоустойчивых опций для этого можно было бы упорядочить массив, чтобы гарантировать, что значение по умолчанию является последним (немного расточительным), или добавить значение по умолчанию в переменную, которая будет обработана после foreach(что случится, только если совпадение не найдено)
musefan 14.08.2012 07:59:23
Что если отправитель нулевой? GetType сгенерирует исключение
Jon 2.07.2014 17:32:55
Два предложения: Обрабатывать нулевой источник, вызывая default или выбрасывая исключение, и избавляться от логического значения в CaseInfo, просто проверяя значение типа (если оно равно null, это значение по умолчанию).
Felix K. 19.08.2014 10:25:51

Одним из вариантов является наличие словаря от Typeдо Action(или некоторого другого делегата). Найдите действие, основанное на типе, и затем выполните его. Я использовал это для заводов до сих пор.

101
18.11.2008 15:07:42
Незначительное примечание: хорошо для совпадений 1: 1, но может быть проблемой с наследованием и / или интерфейсами - тем более, что порядок не гарантируется для словаря. Но, тем не менее, я так делаю в некоторых местах ;-p1 +1
Marc Gravell♦ 18.11.2008 15:34:40
@Marc: Как наследование или интерфейсы сломаются в этой парадигме? Если предположить, что ключ является типом, а действие - методом, то, насколько я могу судить, наследование или интерфейсы должны на самом деле форсировать правильную вещь (TM). Я, конечно, понимаю проблему с несколькими действиями и отсутствием порядка.
Harper Shelby 18.11.2008 15:53:31
Я использовал эту технику много раз в прошлом, обычно до перехода на контейнер IoC
Chris Canal 18.11.2008 16:00:38
Этот метод не подходит для наследования и интерфейсов, потому что вам нужно взаимно-однозначное соответствие между проверяемым объектом и вызываемым делегатом. Какой из многочисленных интерфейсов объекта вы должны попытаться найти в словаре?
Robert Rossney 18.11.2008 18:38:10
Если вы создаете словарь специально для этой цели, вы можете перегрузить индексатор, чтобы он возвращал значение типа ключа, или, если он отсутствует, то его суперкласс, если он отсутствует, то этот суперкласс и т. Д., Пока не останется ничего.
Erik Forbes 4.02.2009 23:26:10

Создайте суперкласс (S) и сделайте так, чтобы A и B наследовали его. Затем объявите абстрактный метод на S, который должен реализовать каждый подкласс.

Делая это, метод "foo" также может изменить свою подпись на Foo (S o), что делает его безопасным для типов, и вам не нужно бросать это безобразное исключение.

14
18.11.2008 15:07:58
Правда бруно, но вопрос не говорит об этом. Вы можете включить это в свой ответ, хотя Пабло.
Dana the Sane 18.11.2008 15:28:27
Из вопроса я думаю, что A и B достаточно универсальны, чтобы они могли быть A = String; B = перечислите <int>, например ...
bruno conde 18.11.2008 15:35:15

В C # 7 , поставляемом с Visual Studio 2017 (выпуск 15. *), вы можете использовать типы в caseвыражениях (сопоставление с образцом):

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

В C # 6 вы можете использовать оператор switch с оператором nameof () (спасибо @Joey Adams):

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

В C # 5 и более ранних версиях вы можете использовать оператор switch, но вам придется использовать магическую строку, содержащую имя типа ... которая не особенно удобна для рефакторинга (спасибо @nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}
285
14.12.2017 06:58:43
это работает с case typeof (string) .Name: ... или это должно быть с Valuetype?
Tomer W 12.06.2012 08:47:21
Запутывание может сломать это
Konrad Morawski 18.03.2014 21:28:29
@nukefusion: То есть, если вы не используете блестящий новый nameof()оператор .
Joey Adams 28.01.2015 14:57:16
Мне не нравится этот ответ, потому что nameof (NamespaceA.ClassC) == nameof (NamespaceB.ClassC) имеет значение true.
ischas 19.11.2015 10:38:12
(c # 7) вы также можете использовать подчеркивание, если вам не нужен доступ к объекту:case UnauthorizedException _:
Assaf S. 7.03.2018 10:39:19

Я бы либо

  • использовать перегрузку метода (так же, как x0n ), или
  • использовать подклассы (так же, как Пабло ), или
  • применить шаблон посетителя .
4
23.05.2017 12:26:36

В таких случаях я обычно получаю список предикатов и действий. Что-то в этом роде:

class Mine {
    static List<Func<object, bool>> predicates;
    static List<Action<object>> actions;

    static Mine() {
        AddAction<A>(o => o.Hop());
        AddAction<B>(o => o.Skip());
    }

    static void AddAction<T>(Action<T> action) {
        predicates.Add(o => o is T);
        actions.Add(o => action((T)o);
    }

    static void RunAction(object o) {
        for (int i=0; o < predicates.Count; i++) {
            if (predicates[i](o)) {
                actions[i](o);
                break;
            }
        }
    }

    void Foo(object o) {
        RunAction(o);
    }
}
2
28.08.2019 09:25:16

Вы действительно должны перегружать свой метод, а не пытаться делать устранение неоднозначности самостоятельно. Большинство ответов до сих пор не учитывают будущие подклассы, что в дальнейшем может привести к действительно ужасным проблемам обслуживания.

7
18.11.2008 15:16:34
Разрешение перегрузки определяется статически, так что просто не будет работать вообще.
Neutrino 10.06.2013 19:18:38
@Neutrino: в этом вопросе нет ничего, что указывало бы, что тип не известен во время компиляции. И если это так, перегрузка имеет куда больше смысла, чем любая другая опция, учитывая оригинальный пример кода OP.
Peter Duniho 13.05.2017 04:21:19
Я думаю, тот факт, что он пытается использовать оператор «if» или «switch» для определения типа, является довольно четким признаком того, что тип не известен во время компиляции.
Neutrino 13.05.2017 11:37:00
@Neutrino, я помню, что, как отметил Сергей Березовский, в C # есть динамическое ключевое слово, представляющее тип, который должен решаться динамически (во время выполнения, а не во время компиляции).
Davide Cannizzo 28.08.2019 08:49:16

После сравнения вариантов нескольких ответов, представленных здесь, с возможностями F #, я обнаружил, что в F # улучшена поддержка переключения на основе типов (хотя я все еще придерживаюсь C #).
Возможно, вы захотите увидеть здесь и здесь .

2
16.09.2019 07:02:43
<вставить штекер для F # здесь>
Overlord Zurg 19.06.2015 18:21:18

Я согласен с Джоном по поводу хэша действий для имени класса. Если вы сохраняете свой шаблон, вы можете использовать вместо этого конструкцию «as»:

A a = o as A;
if (a != null) {
    a.Hop();
    return;
}
B b = o as B;
if (b != null) {
    b.Skip();
    return;
}
throw new ArgumentException("...");

Разница в том, что когда вы используете шаблон, if (foo is Bar) {((Bar) foo) .Action (); } вы делаете приведение типов дважды. Теперь, возможно, компилятор оптимизирует и выполнит эту работу только один раз - но я бы не стал рассчитывать на это.

0
18.11.2008 15:23:21
Мне действительно не нравятся множественные точки выхода (возврата), но если вы хотите придерживаться этого, добавьте «if (o == null) throw» в начале, так как позже вы не будете знать, если приведение не было успешным, или объект был нулевым
Sunny Milenov 18.11.2008 15:29:14

Создайте интерфейс IFooable, затем создайте свой класс Aи Bкласс для реализации общего метода, который, в свою очередь, вызывает соответствующий метод, который вы хотите:

interface IFooable
{
    public void Foo();
}

class A : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Hop();
    }
}

class B : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Skip();
    }
}

class ProcessingClass
{
    public void Foo(object o)
    {
        if (o == null)
            throw new NullRefferenceException("Null reference", "o");

        IFooable f = o as IFooable;
        if (f != null)
        {
            f.Foo();
        }
        else
        {
            throw new ArgumentException("Unexpected type: " + o.GetType());
        }
    }
}

Обратите внимание, что лучше использовать asвместо этого сначала проверку с isпоследующим приведением, а затем приведение, так как вы делаете 2 приведения, так что это дороже.

2
28.08.2019 08:57:52

Другой способ - определить интерфейс IThing, а затем реализовать его в обоих классах. Вот фрагмент кода:

public interface IThing
{
    void Move();
}

public class ThingA : IThing
{
    public void Move()
    {
        Hop();
    }

    public void Hop(){  
        //Implementation of Hop 
    }

}

public class ThingA : IThing
{
    public void Move()
    {
        Skip();
    }

    public void Skip(){ 
        //Implementation of Skip    
    }

}

public class Foo
{
    static void Main(String[] args)
    {

    }

    private void Foo(IThing a)
    {
        a.Move();
    }
}
4
18.11.2008 15:57:21

Если бы вы использовали C # 4, вы могли бы использовать новую динамическую функциональность для создания интересной альтернативы. Я не говорю, что это лучше, на самом деле кажется, что это будет медленнее, но в нем есть определенная элегантность.

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}

И использование:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

Причина, по которой это работает, заключается в том, что при вызове динамического метода C # 4 перегрузки разрешаются во время выполнения, а не во время компиляции. Я написал немного больше об этой идее совсем недавно . Опять же, я просто хотел бы повторить, что это, вероятно, работает хуже, чем все другие предложения, я предлагаю это просто из любопытства.

8
19.11.2008 15:22:38
У меня была та же идея сегодня. Это примерно в 3 раза медленнее, чем переключение имени типа. Конечно, медленнее относительно (для 60 000 000 звонков, всего 4 секунды), и код гораздо более читабелен, это того стоит.
Daryl 21.11.2013 15:24:34

Учитывая, что наследование облегчает распознавание объекта как более одного типа, я думаю, что переключение может привести к плохой неопределенности. Например:

Случай 1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

Дело 2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

Потому что s это строка и объект. Я думаю, что когда вы пишете, switch(foo)вы ожидаете, что foo будет соответствовать одному и только одному из caseутверждений. С переключением типов порядок, в котором вы пишете операторы case, может изменить результат всего оператора switch. Я думаю, что это было бы неправильно.

Можно подумать о проверке компилятором типов оператора «typeswitch», проверяющего, что перечисляемые типы не наследуются друг от друга. Этого не существует, хотя.

foo is Tэто не то же самое, что foo.GetType() == typeof(T)!!

5
15.04.2016 22:39:45

С ответом JaredPar в затылке я написал вариант его TypeSwitchкласса, который использует вывод типов для лучшего синтаксиса:

class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }

public string GetName(object value)
{
    string name = null;
    TypeSwitch.On(value)
        .Case((C x) => name = x.FullName)
        .Case((B x) => name = x.LongName)
        .Case((A x) => name = x.Name)
        .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
        .Case((Y x) => name = x.GetIdentifier())
        .Default((x) => name = x.ToString());
    return name;
}

Обратите внимание, что порядок Case()методов важен.


Получить полный и прокомментированный код для моего TypeSwitchкласса . Это рабочая сокращенная версия:

public static class TypeSwitch
{
    public static Switch<TSource> On<TSource>(TSource value)
    {
        return new Switch<TSource>(value);
    }

    public sealed class Switch<TSource>
    {
        private readonly TSource value;
        private bool handled = false;

        internal Switch(TSource value)
        {
            this.value = value;
        }

        public Switch<TSource> Case<TTarget>(Action<TTarget> action)
            where TTarget : TSource
        {
            if (!this.handled && this.value is TTarget)
            {
                action((TTarget) this.value);
                this.handled = true;
            }
            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!this.handled)
                action(this.value);
        }
    }
}
49
23.05.2017 12:34:48
Выглядит как хорошее решение и хотел посмотреть, что еще ты хотел сказать по этому поводу, но блог мертв.
Wes Grant 27.06.2012 00:58:38
Черт, ты прав. Мой веб-хостинг имеет некоторые проблемы с часа. Они работают над этим. Пост в моем блоге по сути такой же, как и ответ здесь, но со ссылкой на полный исходный код.
Daniel A.A. Pelsmaeker 27.06.2012 01:36:59
Мне нравится, как это сводит кучу скобок к простому «функциональному» переключателю. Хорошая работа!
James White 11.06.2014 17:22:21
Кроме того, можно добавить метод расширения для исходного случая: public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource. Это позволяет вам сказатьvalue.Case((C x) ...
Joey Adams 27.01.2015 21:10:57
@JoeyAdams: я включил ваше последнее предложение вместе с некоторыми небольшими улучшениями. Тем не менее, я придерживаюсь синтаксиса.
Daniel A.A. Pelsmaeker 27.01.2015 23:07:05

Как полагает Пабло, интерфейсный подход - почти всегда правильная вещь, чтобы справиться с этим. Чтобы по-настоящему использовать switch, другой альтернативой является наличие собственного перечисления, обозначающего ваш тип в ваших классах.

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

Это также реализовано в BCL. Один пример - MemberInfo.MemberTypes , другой - GetTypeCodeдля примитивных типов, например:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}
0
23.05.2017 11:47:26

Вы можете создавать перегруженные методы:

void Foo(A a) 
{ 
    a.Hop(); 
}

void Foo(B b) 
{ 
    b.Skip(); 
}

void Foo(object o) 
{ 
    throw new ArgumentException("Unexpected type: " + o.GetType()); 
}

И приведите аргумент к dynamicтипу, чтобы обойти статическую проверку типов:

Foo((dynamic)something);
3
28.08.2019 08:57:39

Мне понравилось использование Virtlink неявной типизации, чтобы сделать переключение более читабельным, но мне не понравилось, что ранний выход невозможен, и что мы делаем распределение. Давайте немного прибавим.

public static class TypeSwitch
{
    public static void On<TV, T1>(TV value, Action<T1> action1)
        where T1 : TV
    {
        if (value is T1) action1((T1)value);
    }

    public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
        where T1 : TV where T2 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
    }

    public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
        where T1 : TV where T2 : TV where T3 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
        else if (value is T3) action3((T3)value);
    }

    // ... etc.
}

Ну, это заставляет мои пальцы болеть. Давайте сделаем это в T4:

<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.IO" #> 
<#
    string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
    const int MaxCases = 15;
#>
<#=GenWarning#>

using System;

public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
    var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
    var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
    var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
    <#=GenWarning#>

    public static void On<TV, <#=types#>>(TV value, <#=actions#>)
        <#=wheres#>
    {
        if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
        else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
    }

<#}#>
    <#=GenWarning#>
}

Настроим пример Virtlink немного:

TypeSwitch.On(operand,
    (C x) => name = x.FullName,
    (B x) => name = x.LongName,
    (A x) => name = x.Name,
    (X x) => name = x.ToString(CultureInfo.CurrentCulture),
    (Y x) => name = x.GetIdentifier(),
    (object x) => name = x.ToString());

Читается и быстро. Теперь, когда все продолжают указывать в своих ответах и ​​учитывая природу этого вопроса, порядок соответствия важен при сопоставлении типов. Следовательно:

  • Сначала укажите типы листьев, а потом базовые.
  • Для пировых типов сначала ставьте более вероятные совпадения, чтобы максимизировать производительность.
  • Это означает, что нет необходимости в специальном случае по умолчанию. Вместо этого просто используйте самый базовый тип в лямбде и поместите его последним.
6
23.05.2017 12:02:47

Для встроенных типов вы можете использовать перечисление TypeCode. Обратите внимание, что GetType () довольно медленный, но, вероятно, не подходит в большинстве ситуаций.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

Для пользовательских типов вы можете создать собственное перечисление, а также интерфейс или базовый класс с абстрактным свойством или методом ...

Абстрактный класс реализации свойства

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
    public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Реализация класса абстрактного метода

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
    public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Интерфейсная реализация свойства

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Интерфейсная реализация метода

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Один из моих коллег только что рассказал мне об этом: у этого есть то преимущество, что вы можете использовать его буквально для любого типа объекта, а не только для того, который вы определяете. Недостатком является то, что он немного больше и медленнее.

Сначала определите статический класс следующим образом:

public static class TypeEnumerator
{
    public class TypeEnumeratorException : Exception
    {
        public Type unknownType { get; private set; }
        public TypeEnumeratorException(Type unknownType) : base()
        {
            this.unknownType = unknownType;
        }
    }
    public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
    private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
    static TypeEnumerator()
    {
        typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
        typeDict[typeof(int)] = TypeEnumeratorTypes._int;
        typeDict[typeof(string)] = TypeEnumeratorTypes._string;
        typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
        typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
    }
    /// <summary>
    /// Throws NullReferenceException and TypeEnumeratorException</summary>
    /// <exception cref="System.NullReferenceException">NullReferenceException</exception>
    /// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
    public static TypeEnumeratorTypes EnumerateType(object theObject)
    {
        try
        {
            return typeDict[theObject.GetType()];
        }
        catch (KeyNotFoundException)
        {
            throw new TypeEnumeratorException(theObject.GetType());
        }
    }
}

И тогда вы можете использовать это так:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}
7
15.11.2013 22:37:05
Спасибо за добавление TypeCode () - варианта для примитивных типов, потому что даже вариант C # 7.0 - не работает с ними (как и nameof (), очевидно)
Ole Albers 14.09.2017 19:08:50

Это альтернативный ответ, который смешивает вклады ответов JaredPar и VirtLink со следующими ограничениями:

  • Конструкция переключателя ведет себя как функция и получает функции в качестве параметров для случаев.
  • Гарантирует, что он правильно построен, и всегда существует функция по умолчанию .
  • Он возвращается после первого матча (верно для JaredPar ответа, не верно для VirtLink одного).

Использование:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

Код:

public class TSwitch<TResult>
{
    class CaseInfo<T>
    {
        public Type Target { get; set; }
        public Func<object, T> Func { get; set; }
    }

    private object _source;
    private List<CaseInfo<TResult>> _cases;

    public static TSwitch<TResult> On(object source)
    {
        return new TSwitch<TResult> { 
            _source = source,
            _cases = new List<CaseInfo<TResult>>()
        };
    }

    public TResult Default(Func<object, TResult> defaultFunc)
    {
        var srcType = _source.GetType();
       foreach (var entry in _cases)
            if (entry.Target.IsAssignableFrom(srcType))
                return entry.Func(_source);

        return defaultFunc(_source);
    }

    public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
    {
        _cases.Add(new CaseInfo<TResult>
        {
            Func = x => func((TSource)x),
            Target = typeof(TSource)
        });
        return this;
    }
}
0
23.08.2016 10:59:41

Да, благодаря C # 7 этого можно достичь. Вот как это делается (с использованием шаблона выражения ):

switch (o)
{
    case A a:
        a.Hop();
        break;
    case B b:
        b.Skip();
        break;
    case C _: 
        return new ArgumentException("Type C will be supported in the next version");
    default:
        return new ArgumentException("Unexpected type: " + o.GetType());
}
8
28.08.2019 09:08:06
Florian Koch 6.04.2017 07:12:55

Вы ищете Discriminated Unionsкакую-либо языковую особенность F #, но вы можете добиться аналогичного эффекта, используя созданную мной библиотеку под названием OneOf

https://github.com/mcintyre321/OneOf

Основное преимущество перед switchifи exceptions as control flow) состоит в том, что он безопасен во время компиляции - нет обработчика по умолчанию или сбой

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

Если вы добавите третий элемент в o, вы получите ошибку компилятора, так как вам нужно добавить обработчик Func внутри вызова switch.

Вы также можете сделать, .Matchкоторый возвращает значение, а не выполняет оператор:

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}
2
8.08.2017 20:27:28

Согласно спецификации C # 7.0, вы можете объявить локальную переменную в caseобласти switch:

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}

Это лучший способ сделать это, потому что он включает в себя только операции приведения и «толкания в стеке», которые являются самыми быстрыми операциями, которые интерпретатор может выполнить сразу после побитовых операций и booleanусловий.

Сравнивая это с a Dictionary<K, V>, мы получаем гораздо меньшее использование памяти: для удержания словаря требуется больше места в оперативной памяти, а процессор требует больше вычислений для создания двух массивов (один для ключей и другой для значений) и сбора хэш-кодов для ключей, которые нужно поместить значения их соответствующих ключей.

Итак, насколько я знаю, я не верю, что более быстрый способ мог бы существовать, если вы не хотите использовать только if- then- elseблок с isоператором следующим образом:

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.
4
28.08.2019 08:42:58

Да - просто используйте слегка странное название «сопоставление с образцом» из C # 7 и выше, чтобы сопоставить класс или структуру:

IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}
0
29.10.2018 12:05:17

Я бы создал интерфейс с любым именем и именем метода, которое имело бы смысл для вашего коммутатора, давайте назовем их соответственно: IDoableэто говорит о необходимости реализации void Do().

public interface IDoable
{
    void Do();
}

public class A : IDoable
{
    public void Hop() 
    {
        // ...
    }

    public void Do()
    {
        Hop();
    }
}

public class B : IDoable
{
    public void Skip() 
    {
        // ...
    }

    public void Do()
    {
        Skip();
    }
}

и измените метод следующим образом:

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

По крайней мере, с этим вы в безопасности во время компиляции, и я подозреваю, что с точки зрения производительности это лучше, чем проверка типа во время выполнения.

1
14.01.2019 15:56:05

я использую

    public T Store<T>()
    {
        Type t = typeof(T);

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }
0
28.11.2018 23:04:51

Вы можете использовать сопоставление с образцом в C # 7 или выше:

switch (foo.GetType())
{
    case var type when type == typeof(Player):
        break;
    case var type when type == typeof(Address):
        break;
    case var type when type == typeof(Department):
        break;
    case var type when type == typeof(ContactType):
        break;
    default:
        break;
}
11
16.09.2019 05:14:07
Спасибо за это! Может также использоваться для обнаружения подклассов: if (this.TemplatedParent.GetType (). IsSubclassOf (typeof (RadGridView))) можно изменить на: switch (this.TemplatedParent.GetType ()) case var subRadGridView при subRadGridView.IsSubclassOf ( typeof (RadGridView)):
Flemming Bonde Kentved 25.04.2019 13:39:45
Ты делаешь это неправильно. Посмотрите ответ Сергея Интернна и прочитайте о принципе замены Лискова
0xF 5.02.2020 15:50:52

Должен работать с

тип дела _:

подобно:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}
0
4.04.2019 12:56:32

Если вы знаете ожидаемый класс, но у вас все еще нет объекта, вы даже можете сделать это:

private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
    switch (new T())
    {
        case BaseClassReview _: return "Review";
        case BaseClassValidate _: return "Validate";
        case BaseClassAcknowledge _: return "Acknowledge";
        default: return "Accept";
    }
}
0
8.04.2019 12:42:35

C # 8 улучшений сопоставления с образцом позволили сделать это следующим образом. В некоторых случаях это делает работу и более кратким.

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };
2
14.01.2020 08:50:19

С C # 8 вы можете сделать его еще более лаконичным с новым коммутатором. А с помощью опции discard _ вы можете избежать создания ненужных переменных, когда они вам не нужны, например:

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

Invoice и ShippingList являются классами, а document является объектом, который может быть любым из них.

0
25.02.2020 02:08:10