Использование Case / Switch и GetType для определения объекта

Возможный дубликат:
C # - Есть ли лучшая альтернатива" включить тип "?

Если вы хотите switch на типе объекта, как лучше всего это сделать?

Фрагмент кода

private int GetNodeType(NodeDTO node)
{
    switch (node.GetType())
    { 
        case typeof(CasusNodeDTO):
            return 1;
        case typeof(BucketNodeDTO):
            return 3;
        case typeof(BranchNodeDTO):
            return 0;
        case typeof(LeafNodeDTO):
            return 2;
        default:
            return -1;
    }
}

Я знаю, что это не так, но мне было интересно, как вы могли бы решить эту проблему. Подходит ли в этом случае оператор if/else?

Или вы используете переключатель и добавляете .ToString() к типу?


person user29964    schedule 02.04.2009    source источник
comment
Если кому-то интересно, Питер Халлам обсуждает, почему это не функция C #, на blogs.msdn.com/peterhal/archive/2005/07/05/435760.aspx   -  person M. Dudley    schedule 25.01.2010
comment
Я знаю, что это 2017 год, и это старый комментарий, однако ... Только что прочитав эту статью Питера Халлама, я теперь в замешательстве. C # 7 позволяет переключаться там, где важен порядок операторов case - конечно, это противоречит тому, что, по-видимому, является одной из основных причин, по которым он не был добавлен в язык?   -  person Wayne Feltham    schedule 07.09.2017
comment
На самом деле вы можете переключать типы в C # 7 ... Я думаю, они передумали (или нашли лучший способ сделать это) через 12 лет: stackoverflow.com/questions/298976/   -  person drojf    schedule 30.08.2018
comment
Связанное замечание: VB.NET имеет встроенную функцию.   -  person Mike    schedule 18.03.2019
comment
Да, похоже, это должно сработать. Вы могли подумать, что typeof () будет разрешен во время компиляции и, следовательно, даст константу для включения во время выполнения, но, увы, нет. Во всяком случае, пока нет. :(   -  person Zeek2    schedule 22.09.2020


Ответы (10)


Если бы мне действительно пришлось switch указать тип объекта, я бы использовал .ToString(). Однако я бы избегал этого любой ценой: IDictionary<Type, int> будет намного лучше, посетитель может оказаться излишним. но в остальном это все еще прекрасное решение.

person Anton Gogolev    schedule 02.04.2009
comment
На мой взгляд, IDictionary - прекрасное решение. Если нужно протестировать более одного или двух типов, я обычно использую это. Ну, или просто используйте в первую очередь полиморфизм, чтобы не переключать типы. - person OregonGhost; 02.04.2009
comment
Полиморфизм там, где это необходимо. Если этот тип используется для сериализации, вы смешиваете проблемы. - person Dave Van den Eynde; 02.04.2009
comment
почему бы не приложить усилия и не привести пример применения IDictionary в указанном случае? - person jasie; 11.05.2021

Это не решит напрямую вашу проблему, поскольку вы хотите включить свои собственные определяемые пользователем типы, но для тех, кто хочет включать только встроенные типы, вы можете использовать TypeCode:

switch (Type.GetTypeCode(node.GetType()))
{
    case TypeCode.Decimal:
        // Handle Decimal
        break;

    case TypeCode.Int32:
        // Handle Int32
        break;
     ...
}
person Ashley    schedule 04.02.2011
comment
Хорошая идея, но, похоже, не работает для пользовательских классов. - person Samik R; 08.12.2011
comment
Нет, все остальное просто вернет «Объект». - person Ashley; 09.12.2011
comment
@splattne - Просто любопытно, почему именно нужно было редактировать отступ? - person Ashley; 20.09.2013
comment
@Ashley Я исправил фрагмент, потому что ... не были частью блока кода. См. imgur.com/CfTIzTU. Исправление отступов было побочным продуктом. :-) - person splattne; 20.09.2013
comment
@splattne «...» не должно было быть частью кода, потому что «...» на самом деле не является кодом. Однако я видел аргумент в пользу облегчения чтения. Однако отступы ... Я не знаю, как вы можете назвать это «исправлением», просто потому, что вам это нравится сейчас. Я не вижу никаких рекомендаций StackOverflow о том, как делать отступы в коде. Только в этом вопросе есть множество стилей. - person Ashley; 20.09.2013
comment
это неплохо для примитивных типов, но когда у вас есть массивы или другие сложные типы, вам нужно использовать if / else - person dmihailescu; 13.02.2015
comment
Да, но иногда это то, что вам нужно. Я как раз сейчас пишу интеграцию библиотеки диаграмм. Данные поступают из Серии ‹T› и должны быть одним из примитивов - ИЛИ структурой, которая затем содержит примитивы в качестве свойств. GetTypeCode - это все, что мне нужно. НИЦЦА. - person TomTom; 27.03.2016
comment
Не работает для enum - person Joke Huang; 07.11.2019
comment
Как сказано в ответе, он работает только для встроенных типов. Это решение, которое я искал. Спасибо @Ashley. - person ; 03.05.2020

В сообщении блога MSDN Многие вопросы: включите тип - это некоторая информация о том, почему .NET не поддерживает переключение типов. .

Как обычно - всегда есть обходные пути.

Это не мой, но, к сожалению, я потерял источник. Это делает возможным переключение типов, но лично я считаю это довольно неудобным (идея словаря лучше):

  public class Switch
  {
      public Switch(Object o)
      {
          Object = o;
      }

      public Object Object { get; private set; }
  }


  /// <summary>
  /// Extensions, because otherwise casing fails on Switch==null
  /// </summary>
  public static class SwitchExtensions
  {
      public static Switch Case<T>(this Switch s, Action<T> a)
            where T : class
      {
          return Case(s, o => true, a, false);
      }

      public static Switch Case<T>(this Switch s, Action<T> a,
           bool fallThrough) where T : class
      {
          return Case(s, o => true, a, fallThrough);
      }

      public static Switch Case<T>(this Switch s,
          Func<T, bool> c, Action<T> a) where T : class
      {
          return Case(s, c, a, false);
      }

      public static Switch Case<T>(this Switch s,
          Func<T, bool> c, Action<T> a, bool fallThrough) where T : class
      {
          if (s == null)
          {
              return null;
          }

          T t = s.Object as T;
          if (t != null)
          {
              if (c(t))
              {
                  a(t);
                  return fallThrough ? s : null;
              }
          }

          return s;
      }
  }

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

 new Switch(foo)
     .Case<Fizz>
         (action => { doingSomething = FirstMethodCall(); })
     .Case<Buzz>
         (action => { return false; })
person Arnis Lapsa    schedule 15.09.2009
comment
Довольно круто, хотя это довольно дорогой шаблон, который требует относительно большого количества времени в сборке мусора. Но все равно очень читабельно ... - person JoeGeeky; 02.11.2010
comment
В статье говорится, что Программисты были бы чрезвычайно удивлены, узнав, что изменение порядка ярлыков случаев повлияло на то, какой регистр был выбран. Я не могу не согласиться. Представьте, что вы окрашиваете указатель уровня топлива в зеленый / оранжевый / красный цвет, вы делаете switch percentageFuelRemaining, затем case > 75 case > 50, case > 25. - person NibblyPig; 07.10.2015
comment
Классное решение, но я бы использовал его только один раз, а не регулярно во время выполнения программы. Отражение дорого. Отлично подходит для обработки нескольких исключений и сообщений об ошибках и т. Д., Но если вы используете его сотни раз, это плохое решение. - person rolls; 31.10.2016

Я столкнулся с той же проблемой и наткнулся на этот пост. Это то, что подразумевается под подходом IDictionary:

Dictionary<Type, int> typeDict = new Dictionary<Type, int>
{
    {typeof(int),0},
    {typeof(string),1},
    {typeof(MyClass),2}
};

void Foo(object o)
{
    switch (typeDict[o.GetType()])
    {
        case 0:
            Print("I'm a number.");
            break;
        case 1:
            Print("I'm a text.");
            break;
        case 2:
            Print("I'm classy.");
            break;
        default:
            break;
    }
}

Если так, я не могу сказать, что я фанат согласования чисел в словаре с операторами case.

Это было бы идеально, но ссылка на словарь убивает его:

void FantasyFoo(object o)
{
    switch (typeDict[o.GetType()])
    {
        case typeDict[typeof(int)]:
            Print("I'm a number.");
            break;
        case typeDict[typeof(string)]:
            Print("I'm a text.");
            break;
        case typeDict[typeof(MyClass)]:
            Print("I'm classy.");
            break;
        default:
            break;
    }
}

Есть ли еще одна реализация, которую я упустил?

person bjaxbjax    schedule 02.12.2009
comment
Возможно, вы могли бы создать перечисление, которое заняло бы место int в вашем словаре типов? Это должно избавить ваш код от этих надоедливых магических чисел. - person TROD; 26.06.2017

Я бы просто использовал оператор if. В таком случае:

Type nodeType = node.GetType();
if (nodeType == typeof(CasusNodeDTO))
{
}
else ... 

Другой способ сделать это:

if (node is CasusNodeDTO)
{
}
else ...

Первый пример верен только для точных типов, где последний также проверяет наследование.

person David Wengier    schedule 02.04.2009
comment
Я поддерживаю это, но я думаю, что сравнение ссылок происходит быстрее, чем повторные попытки приведения типов. - person Dave Van den Eynde; 02.04.2009
comment
Я не уверен, что это сравнение ссылок. Я думаю, что система RuntimeType вступает в силу. Я просто догадываюсь, потому что, если бы это было не так, компилятор не сказал бы вам, что typeof (X) не является константой - person David Wengier; 02.04.2009
comment
проверка второго типа выполняется медленнее, поскольку проверяет всю иерархию классов. - person msfanboy; 13.08.2011

Ты можешь это сделать:

function void PrintType(Type t) {
 var t = true;
 new Dictionary<Type, Action>{
   {typeof(bool), () => Console.WriteLine("bool")},
   {typeof(int),  () => Console.WriteLine("int")}
 }[t.GetType()]();
}

Это понятно и легко. Это немного медленнее, чем где-то кешировать словарь ... но для большого количества кода это все равно не имеет значения ...

person nreyntje    schedule 04.08.2010
comment
Кто-нибудь хочет прокомментировать, почему это было отклонено? Что насчет того, что он неверен или не работает? - person Norman H; 14.02.2012
comment
Не думаю, что я бы стал так поступать, но только из эстетических соображений (правда, немного глупо). Тем не менее, мне нравится видеть, как люди думают нестандартно, и это отличное использование лямбда-выражений :) - person LOAS; 08.03.2012
comment
Это элегантное решение, которое эффективно для большого количества типов и ясно передает намерения авторов. - person Rupert Rawnsley; 30.03.2014
comment
Это самое чистое решение для этого вопроса. - person Vasil Popov; 30.05.2019
comment
Немного наивное, но лаконичное решение. Если бы я был OP, я бы принял это как ответ, потому что ... ну ... мне нравятся лямбды: P - person gvdm; 28.11.2019

Ты можешь это сделать:

if (node is CasusNodeDTO)
{
    ...
}
else if (node is BucketNodeDTO)
{
    ...
}
...

Хотя это было бы более элегантно, возможно, это не так эффективно, как некоторые другие ответы здесь.

person Dave Van den Eynde    schedule 02.04.2009
comment
После проведения некоторых тестов производительности я полностью согласен с тем, что использование if else - лучший вариант для этих типов проверок, использование непрерывного вызова метода в значительной степени плохо, поскольку они никогда не прекратятся, даже если совпадение будет найдено очень рано (если вы не генерировать исключение, которое остановит вызов других методов, но все равно очень плохое использование) - person Ahmed Fwela; 21.11.2016

Один из подходов - добавить в NodeDTO чистый виртуальный метод GetNodeType () и переопределить его в потомках, чтобы каждый потомок возвращал фактический тип.

person sharptooth    schedule 02.04.2009
comment
Хотя это объектно-ориентированный способ справиться с этим, вы можете решить, что Node не должен поддерживать ничего из этого. - person Dave Van den Eynde; 02.04.2009
comment
Большой +1 здесь и Джейсону Койну. Еще никто не читал книгу «Рефакторинг»? Это пример из учебника: refactoring.com/catalog/replaceConditionalWithPolymorphism.html - person TrueWill; 04.08.2010

В зависимости от того, что вы делаете в операторе switch, правильный ответ - полиморфизм. Просто поместите виртуальную функцию в интерфейс / базовый класс и переопределите для каждого типа узла.

person Jason Coyne    schedule 02.04.2009

На самом деле я предпочитаю подход, представленный здесь в качестве ответа: Есть ли лучшая альтернатива" включить тип "?

Однако есть хороший аргумент в пользу отказа от реализации каких-либо методов сравнения типов в объектно-ориентированном языке, таком как C #. В качестве альтернативы вы можете расширить и добавить дополнительные необходимые функции, используя наследование.

Этот момент обсуждался в комментариях к блогу авторов здесь: http://blogs.msdn.com/b/jaredpar/archive/2008/05/16/switching-on-types.aspx#8553535

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

С уважением, Уэйн

person Wayne Phipps    schedule 06.07.2012