Czy istnieje lepsza alternatywa dla "włącz Typ"?

Widząc, że C# nie może switch na typie (który, jak się domyślam, nie został dodany jako specjalny przypadek, ponieważ is relacje oznaczają, że może mieć zastosowanie więcej niż jeden odrębny case), czy istnieje lepszy sposób na symulowanie przełączania na typ inny niż ten?

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());
    }
}
Author: Davide Cannizzo, 2008-11-18

30 answers

W C# 7 / VS 2017 przełączanie typów jest obsługiwane- Zobacz odpowiedź Zachary ' ego Yatesa poniżej). Aby to zrobić bez dużej instrukcji if / else if / else, musisz pracować z inną strukturą. Jakiś czas temu napisałem post na blogu opisujący jak zbudować strukturę TypeSwitch.

Https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

Wersja skrócona: TypeSwitch jest zaprojektowany aby zapobiec zbędnemu odlewaniu i podać składnię podobną do zwykłej instrukcji switch/case. Na przykład, tutaj jest TypeSwitch w akcji na standardowym zdarzeniu formularza 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"));

Kod TypeSwitch jest w rzeczywistości dość mały i można go łatwo umieścić w projekcie.

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
        };
    }
}
 284
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-23 04:25:33

Z C # 7, który został dostarczony z Visual Studio 2017 (Wydanie 15.* ), możesz używać typów w case poleceniach (pattern matching):

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

W C# 6 możesz użyć instrukcji switch z operatorem nameof () (dzięki @Joey Adams):

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

W C # 5 i wcześniejszych, możesz użyć instrukcji switch, ale będziesz musiał użyć magicznego ciągu zawierającego nazwę typu... co nie jest szczególnie przyjazne dla refactor (dzięki @nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}
 317
Author: Zachary Yates,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-14 06:58:43

Jedną z opcji jest posiadanie słownika od Type do Action (lub innego delegata). Wyszukaj akcję na podstawie typu, a następnie wykonaj ją. Używałem tego wcześniej w fabrykach.

 102
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 15:07:42

Z odpowiedź Jaredpara z tyłu mojej głowy, napisałem wariant jego klasy TypeSwitch, który używa wnioskowania typu dla ładniejszej składni:

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

Zauważ, że kolejność metod Case() jest ważna.


Pobierz pełny i skomentowany kod dla mojej klasy TypeSwitch . Jest to skrócona wersja robocza:

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);
        }
    }
}
 49
Author: Daniel A.A. Pelsmaeker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:48

Tworzy superklasę (klasy) i dziedziczy z niej a I B. Następnie zadeklaruj abstrakcyjną metodę na S, którą każda podklasa musi zaimplementować.

W ten sposób Metoda " foo " może również zmienić swoją sygnaturę na Foo( S o), dzięki czemu jest bezpieczna i nie musisz rzucać tego brzydkiego wyjątku.

 14
Author: Pablo Fernandez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 15:07:58

Możesz użyć pattern matching w C# 7 lub wyżej:

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;
}
 14
Author: alhpe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-16 05:14:07

Tak, dzięki C # 7, które można osiągnąć. Oto jak to zrobić (używając wzór wyrażenia):

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());
}
 9
Author: Serge Intern,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-28 09:08:06

Jeśli używasz C # 4, możesz skorzystać z nowej dynamicznej funkcjonalności, aby uzyskać interesującą alternatywę. Nie mówię, że jest to lepsze, w rzeczywistości wydaje się bardzo prawdopodobne, że będzie wolniejsze, ale ma w sobie pewną elegancję.

class Thing
{

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

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

}

I użycie:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

Powodem, dla którego to działa jest to, że dynamiczne wywołanie metody C# 4 ma swoje przeciążenia rozwiązywane w czasie wykonywania, a nie w czasie kompilacji. Napisałem trochę więcej o tym pomyśle całkiem niedawno . Ponownie chciałbym tylko powtórzyć, że to prawdopodobnie działa gorzej niż wszystkie inne sugestie, oferuję to po prostu jako ciekawostkę.

 8
Author: Paul Batum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-19 15:22:38

Naprawdę powinieneś przeciążać swoją metodę, a nie próbować zrobić disambiguation samemu. Większość odpowiedzi do tej pory nie bierze pod uwagę przyszłych podklas, co może później prowadzić do naprawdę strasznych problemów z konserwacją.

 7
Author: sep332,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 15:16:34

Dla typów wbudowanych, można użyć typecode enumeration. Zwróć uwagę, że GetType() jest powolne, ale prawdopodobnie nie ma znaczenia w większości sytuacji.

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

Dla typów niestandardowych, można utworzyć własne wyliczenie, a także interfejs lub klasę bazową z abstrakcyjną właściwością lub metodą...

Implementacja klasy abstrakcyjnej własności

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

Implementacja metody klasy abstrakcyjnej

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

Implementacja interfejsu właściwości

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Interfejs implementacja metody

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Jeden z moich współpracowników właśnie mi o tym powiedział: ma to tę zaletę, że można go używać do dosłownie każdego rodzaju obiektu, a nie tylko tych, które definiujesz. Ma tę wadę, że jest nieco większy i wolniejszy.

Najpierw zdefiniuj statyczną klasę w ten sposób:

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

A potem możesz użyć go w ten sposób:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}
 7
Author: Edward Ned Harvey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-15 22:37:05

Spodobało mi się użycie domyślnego typowania , aby przełącznik był bardziej czytelny, ale nie podobało mi się, że wczesne wyjście nie jest możliwe i że robimy alokacje. Podkręćmy trochę perf.

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.
}
To sprawia, że bolą mnie palce. Zróbmy to w 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#>
}

Trochę poprawiam przykład:

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

Czytelny i szybki. Teraz, jak każdy wskazuje w swoich odpowiedziach, a biorąc pod uwagę charakter tego pytania, porządek jest ważny w Dopasowanie typu. Dlatego:

  • najpierw typy liści, później typy bazowe.
  • w przypadku typów rówieśników najpierw umieść bardziej prawdopodobne mecze, aby zmaksymalizować perf.
  • oznacza to, że nie ma potrzeby stosowania specjalnego przypadku domyślnego. Zamiast tego po prostu użyj bazy-większość wpisz w lambda, i umieścić go na końcu.
 6
Author: scobi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:47

Biorąc pod uwagę, że dziedziczenie ułatwia rozpoznawanie obiektu jako więcej niż jednego typu, myślę, że przełącznik może prowadzić do złej dwuznaczności. Na przykład:

Przypadek 1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

Przypadek 2

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

Ponieważ s jest ciągiem i obiektem. Myślę, że pisząc switch(foo) spodziewasz się, że foo będzie pasować do jednego i tylko jednego z case oświadczeń. Z przełącznikiem na typy, kolejność w jakiej piszesz swoje wypowiedzi może ewentualnie zmienić wynik całego switch statement. Myślę, że to byłoby złe.

Możesz pomyśleć o kompilatorze-sprawdź typy instrukcji "typeswitch", sprawdzając, czy wymienione typy nie dziedziczą po sobie. Ale to nie istnieje.

foo is T to nie to samo co foo.GetType() == typeof(T)!!

 5
Author: Evren Kuzucuoglu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-15 22:39:45

C# 8 ulepszenia pattern matching umożliwiły zrobienie tego w ten sposób. W niektórych przypadkach to zrobić zadanie i bardziej zwięzłe.

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };
 5
Author: PilgrimViis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-14 08:50:19

I would either

 4
Author: Jonas Kongslund,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:36

Innym sposobem byłoby zdefiniowanie interfejsu i zaimplementowanie go w obu klasach oto snipet:

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();
    }
}
 4
Author: jgarcia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 15:57:21

W C# 7.0 Można zadeklarować zmienną lokalną o zasięgu w {[2] } z 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;
}

Jest to najlepszy sposób na zrobienie takiej rzeczy, ponieważ polega ona na uruchamianiu i wciskaniu na stosie operacji, które są najszybszymi operacjami, jakie interpreter może wykonać tuż po operacjach bitowych i warunkach boolean.

Porównując to do Dictionary<K, V>, tutaj jest znacznie mniejsze zużycie pamięci: trzymanie słownika wymaga więcej miejsca w pamięci RAM i trochę obliczeń bardziej przez procesor do tworzenia dwóch tablic (jedna dla kluczy, a druga dla wartości) i zbierania kodów skrótu dla kluczy, aby umieścić wartości do ich odpowiednich kluczy.

Więc, z tego co wiem, nie wierzę, że szybszy sposób może istnieć, chyba że chcesz użyć tylko if-then-else blok z operatorem is w następujący sposób:

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.
 4
Author: Davide Cannizzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-28 08:42:58

Możesz tworzyć przeciążone metody:

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

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

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

I wrzucić argument do dynamic typ w celu obejścia statycznego sprawdzania typu:

Foo((dynamic)something);
 3
Author: Sergey Berezovskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-28 08:57:39

Szukasz Discriminated Unions które są cechą języka F#, ale możesz osiągnąć podobny efekt używając biblioteki, którą stworzyłem, o nazwie OneOf

Https://github.com/mcintyre321/OneOf

Główną zaletą switch (oraz if i exceptions as control flow) jest to, że jest bezpieczny w czasie kompilacji-nie ma domyślnej obsługi ani fall through

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

Jeśli dodasz trzecią pozycję do o, otrzymasz błąd kompilatora, ponieważ musisz dodać Func obsługi wewnątrz wywołania przełącznika.

Ty może również wykonać .Match, która zwraca wartość, a nie wykonuje instrukcję:

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}
 2
Author: mcintyre321,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-08 20:27:28

Utwórz Interfejs IFooable, a następnie stwórz klasy A i B, aby zaimplementowały wspólną metodę, która z kolei wywoła odpowiednią metodę, którą chcesz:

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

Zauważ, że lepiej użyć as zamiast najpierw sprawdzić za pomocą is, a następnie rzucić, ponieważ w ten sposób można wykonać 2 rzuty, więc jest to droższe.

 2
Author: Sunny Milenov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-28 08:57:52

I takie przypadki zwykle kończę z listą predykatów i działań. Coś w tym stylu:

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
Author: Hallgrim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-28 09:25:16

Po porównaniu opcji kilka odpowiedzi tutaj dostarczonych do funkcji F#, odkryłem, że F# ma o wiele lepsze wsparcie dla przełączania typu (chociaż nadal trzymam się C#).
Możesz zobaczyć Tutaj i tutaj.

 2
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-16 07:02:43

Z C # 8 można uczynić go jeszcze bardziej zwięzły z nowym przełącznikiem. A za pomocą opcji discard _ możesz uniknąć tworzenia zmiennych innecesary, gdy ich nie potrzebujesz, jak to:

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

Invoice i ShippingList to klasy, a document to obiekt, który może być jedną z nich.

 2
Author: Desmond,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-25 02:08:10

Stworzyłbym interfejs z dowolną nazwą i nazwą metody, która miałaby sens dla Twojego przełącznika, nazwijmy je odpowiednio: IDoable który mówi, aby zaimplementować 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();
    }
}

I zmienić metodę w następujący sposób:

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

Przynajmniej z tym, że jesteś bezpieczny w czasie kompilacji i podejrzewam, że pod względem wydajności jest to lepsze niż sprawdzanie typu w czasie wykonywania.

 1
Author: Kerry Perret,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-14 15:56:05

Powinno działać z

Typ Przypadku_:

Jak:

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;
}
 1
Author: jean-maurice Destraz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-04 12:56:32

Jeśli znasz klasę, której oczekujesz, ale nadal nie masz obiektu, możesz to zrobić:

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";
    }
}
 1
Author: Chan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-04-08 12:42:35

Zgadzam się z Jonem o hash działań do nazwy klasy. Jeśli zachowasz swój wzorzec, możesz rozważyć użycie zamiast niego konstrukcji "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("...");

Różnica polega na tym, że gdy używasz wzorca if (foo to Bar) {((Bar) foo).Action ();} wykonujesz dwa razy Casting typu. Teraz może kompilator zoptymalizuje i zrobi to tylko raz - ale nie liczyłbym na to.

 0
Author: plinth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-18 15:23:21

Jak sugeruje Pablo, podejście do interfejsu jest prawie zawsze właściwą rzeczą, aby sobie z tym poradzić. Aby naprawdę wykorzystać przełącznik, inną alternatywą jest posiadanie niestandardowego enum oznaczającego Twój typ w klasach.

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

Jest to tak jakby zaimplementowane również w BCL. Jednym z przykładów jest Membinfo.Membertypy , innym jest GetTypeCode dla typów prymitywnych, takich jak:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}
 0
Author: nawfal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

Jest to alternatywna odpowiedź, która łączy odpowiedzi JaredPar i VirtLink z następującymi ograniczeniami:

  • konstrukcja przełącznika zachowuje się jak funkcjai odbiera Funkcje jako parametry przypadkom.
  • zapewnia, że jest poprawnie zbudowana i zawsze istnieje domyślna funkcja .
  • to powraca po pierwszym meczu (prawda dla JaredPar odpowiedz, nie prawda dla VirtLink jeden).

Użycie:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

Kod:

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;
    }
}
 0
Author: jruizaranguren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-23 10:59:41

Tak-po prostu użyj nieco dziwnie nazwanego "pattern matching" z C # 7 w górę, aby dopasować klasę lub strukturę:

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

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}
 0
Author: James Harcourt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-29 12:05:17

Używam

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

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }
 0
Author: mdimai666,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-28 23:04:51