Есть ли лучшая альтернатива, чем эта, чтобы «включить тип»? - PullRequest
299 голосов
/ 18 ноября 2008

Видя, как C # не может переключить на тип (который, как я понимаю, не был добавлен в качестве особого случая, потому что отношения - это означает, что более чем один отдельный случай может применить), есть ли лучший способ для имитации переключения типа, чем этот?

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());
    }
}

Ответы [ 28 ]

5 голосов
/ 05 апреля 2017

Да, благодаря 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());
        }
4 голосов
/ 18 ноября 2008

Я бы либо

  • использовать перегрузку метода (как x0n ) или
  • использовать подклассы (как Pablo ) или
  • применить шаблон посетителей .
4 голосов
/ 26 января 2019

Используйте 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;
        }
3 голосов
/ 18 ноября 2008

Другим способом было бы определить интерфейс 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();
    }
}
3 голосов
/ 18 ноября 2008

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

2 голосов
/ 18 ноября 2008

Создайте интерфейс 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 голосов
/ 04 января 2013

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

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

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

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

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

Foo((dynamic)something);
2 голосов
/ 18 ноября 2008

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

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);
  }
}
1 голос
/ 12 сентября 2018

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

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

Вы случайно не спрашиваете, почему переменная объявлена ​​как string _? Почему подчеркивание?

Ну, еще одна особенность, появившаяся в C # 7.0, заключается в том, что вы можете называть такую ​​переменную, на которую вы никогда не ссылаетесь. Таким образом, вы не можете ссылаться на переменную _. Это хорошо во многих сценариях, таких как запрос OP, поскольку он хочет просто проверить тип и не получить также приведенную ссылку. В противном случае вы можете переименовать эту переменную и использовать ее в качестве справочной информации.


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

Сравнивая это с 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.
1 голос
/ 08 ноября 2018

Я бы создал интерфейс с любым именем и именем метода, которое имело бы смысл для вашего коммутатора, давайте назовем их соответственно: 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();
    // ...
}

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

...