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

Видя, что С# не может 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());
    }
}

Ответ 1

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

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

Короткая версия: 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
        };
    }
}

Ответ 2

В С# 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));
}

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

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

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

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

Ответ 3

Один из вариантов - иметь словарь от Type до Action (или другого делегата). Посмотрите действие, основанное на типе, и затем выполните его. Раньше я использовал это для заводов.

Ответ 4

С ответом 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);
        }
    }
}

Ответ 5

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

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

Ответ 6

Если вы использовали С# 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);

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

Ответ 7

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

Ответ 8

Для встроенных типов вы можете использовать перечисление 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;
}

Ответ 9

Мне понравился 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());

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

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

Ответ 10

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

Случай 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. При использовании типов переключателей порядок, в котором вы пишете ваши заявления о случаях, может изменить результат всего оператора switch. Я думаю, что это было бы неправильно.

Вы можете подумать о компиляторе-проверке типов оператора "typewitch", проверяя, что перечисленные типы не наследуются друг от друга. Однако этого не существует.

foo is T не совпадает с foo.GetType() == typeof(T)!!

Ответ 11

Да, благодаря С# 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());
}

Ответ 12

Вы можете использовать сопоставление с образцом в С# 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;
}

Ответ 13

Я бы либо

  • используйте метод перегрузки (как x0n) или
  • используйте подклассы (например, Pablo) или
  • примените шаблон .

Ответ 14

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

Ответ 15

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

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

Ответ 16

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

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

Ответ 17

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

Ответ 18

Создайте интерфейс 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 приведения, так что это дороже.

Ответ 19

Согласно спецификации С# 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.

Сравнивая это с 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.

Ответ 20

Вы ищете 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 внутри вызова коммутатора.

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

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

Ответ 21

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

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

Ответ 22

Я согласен с Джоном в том, что у меня есть хеш действий для имени класса. Если вы сохраните свой шаблон, вам может потребоваться использовать вместо него конструкцию "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("...");

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

Ответ 23

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

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 ......
    }
}

Ответ 24

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

  • Конструкция коммутатора ведет себя как функция и принимает функции как параметры к случаям.
  • Обеспечивает правильную сборку и всегда существует функция по умолчанию.
  • возвращается после первого соответствия (true для ответа 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;
    }
}

Ответ 25

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

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

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}

Ответ 26

я использую

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

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }

Ответ 27

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

тип дела _:

лайк:

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

Ответ 28

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

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