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

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
поделиться
Другие вопросы по тегам:

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