Существует ли лучшая альтернатива, чем это для 'включения типа'?

Наблюдение как 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());
    }
}
312
задан Davide Cannizzo 16 September 2019 в 08:08
поделиться

12 ответов

Включению типов определенно недостает C# ( ОБНОВЛЕНИЕ: в C#7 / VS 2017, включая типы поддерживается - , см. ответ Zachary Yates ниже ). Чтобы сделать это без большого если/еще, если/еще оператор, необходимо будет работать с различной структурой. Я записал сообщению в блоге некоторое время назад детализацию, как создать структуру TypeSwitch.

http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx

Короткая версия: TypeSwitch разработан, чтобы предотвратить избыточный кастинг и дать синтаксис, который подобен нормальному переключателю/оператору выбора. Например, вот TypeSwitch в действии со стандартным событием

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"));

формы Windows, код для 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
        };
    }
}
270
ответ дан Austin Theriault 23 November 2019 в 01:10
поделиться

Я соглашаюсь с Jon о наличии хеша действий к имени класса. Если Вы сохраняете свой шаблон, Вы могли бы хотеть рассмотреть использование "как" конструкция вместо этого:

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("...");

различие то, что при использовании скороговорки если (нечто является Панель) {((Панель) нечто).Action ();} Вы делаете преобразование типа дважды. Теперь, возможно, компилятор оптимизирует и только сделает ту работу однажды - но я не рассчитывал бы на него.

0
ответ дан plinth 23 November 2019 в 01:10
поделиться

Создайте интерфейс 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());
   }
}
}

Примечание, которое лучше использовать "как" вместо этого сначала сверение "," и затем кастинг как тот способ, которым Вы делаете 2 броска (дорогими).

2
ответ дан Davide Cannizzo 23 November 2019 в 01:10
поделиться

Иначе должен был бы определить интерфейс 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
ответ дан jgarcia 23 November 2019 в 01:10
поделиться

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

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
ответ дан Davide Cannizzo 23 November 2019 в 01:10
поделиться

Я был бы любой

  • перегрузка метода использования (точно так же, как x0n), или
  • , подклассы использования (точно так же, как Pablo), или
  • применяются шаблон "посетитель" .
4
ответ дан Community 23 November 2019 в 01:10
поделиться

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

7
ответ дан sep332 23 November 2019 в 01:10
поделиться

При использовании 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
ответ дан Paul Batum 23 November 2019 в 01:10
поделиться

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

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

14
ответ дан Pablo Fernandez 23 November 2019 в 01:10
поделиться

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

101
ответ дан Jon Skeet 23 November 2019 в 01:10
поделиться

С 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, можно использовать оператор переключения с nameof (), оператор (благодарит @Joey Adams):

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

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

switch(o.GetType().Name) {
  case "AType":
    break;
}
263
ответ дан Shimmy 23 November 2019 в 01:10
поделиться

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

2
ответ дан Davide Cannizzo 23 November 2019 в 01:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: