Gibt es eine bessere Möglichkeit, das Einschalten eines anderen Typs zu simulieren, da C # switch
für einen Typ nicht möglich ist (was meiner Ansicht nach nicht als Sonderfall hinzugefügt wurde, da is
Beziehungen bedeuten, dass case
möglicherweise mehr als ein eindeutiger Typ gilt)?
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());
}
}
switch
Anweisung enthält, nicht kennen dürfen . Ein Beispiel: Baugruppe A enthält eine Reihe von Datenobjekten (die sich nicht ändern werden, definiert in einem Spezifikationsdokument oder dergleichen). Die Baugruppen B , C und D verweisen jeweils auf A und stellen eine Konvertierung für die verschiedenen Datenobjekte von A bereit (z. B. eine Serialisierung / Deserialisierung in ein bestimmtes Format). Sie müssen entweder die gesamte Klassenhierarchie in B , C und D spiegeln und Fabriken verwenden, oder Sie haben ...Antworten:
Das Einschalten von Typen fehlt in C # definitiv ( UPDATE: In C # 7 / VS 2017 wird das Einschalten von Typen unterstützt - siehe Zachary Yates 'Antwort unten ). Um dies ohne eine große if / else if / else-Anweisung zu tun, müssen Sie mit einer anderen Struktur arbeiten. Ich habe vor einiger Zeit einen Blog-Beitrag geschrieben, in dem beschrieben wird, wie eine TypeSwitch-Struktur erstellt wird.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Kurzversion: TypeSwitch wurde entwickelt, um redundantes Casting zu verhindern und eine Syntax anzugeben, die einer normalen switch / case-Anweisung ähnelt. Hier ist beispielsweise TypeSwitch in Aktion für ein Standard-Windows-Formularereignis
Der Code für TypeSwitch ist eigentlich ziemlich klein und kann einfach in Ihr Projekt eingefügt werden.
quelle
foreach
(nicht gefunden wurde , die nur jemals passieren würde , wenn ein Spiel)CaseInfo
Wert in, indem Sie nur den Typwert überprüfen (wenn er null ist, ist dies der Standardwert).Mit C # 7 , das mit Visual Studio 2017 (Release 15. *) ausgeliefert wurde, können Sie Typen in
case
Anweisungen verwenden (Mustervergleich):Mit C # 6 können Sie eine switch-Anweisung mit dem Operator nameof () verwenden (danke @Joey Adams):
Mit C # 5 und früheren Versionen könnten Sie eine switch-Anweisung verwenden, aber Sie müssen eine magische Zeichenfolge verwenden, die den Typnamen enthält ... was nicht besonders refaktorfreundlich ist (danke @nukefusion).
quelle
nameof()
Operator .case UnauthorizedException _:
Eine Möglichkeit besteht darin, ein Wörterbuch von
Type
bisAction
(oder einen anderen Delegaten) zu haben. Suchen Sie die Aktion anhand des Typs und führen Sie sie dann aus. Ich habe das schon früher für Fabriken benutzt.quelle
Mit JaredPars Antwort im Hinterkopf schrieb ich eine Variante seiner
TypeSwitch
Klasse, die Typinferenz für eine schönere Syntax verwendet:Beachten Sie, dass die Reihenfolge der
Case()
Methoden wichtig ist.Holen Sie sich den vollständigen und kommentierten Code für meine
TypeSwitch
Klasse . Dies ist eine funktionierende Kurzversion:quelle
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. Dies lässt Sie sagenvalue.Case((C x) ...
Erstellen Sie eine Oberklasse (S) und lassen Sie A und B davon erben. Deklarieren Sie dann eine abstrakte Methode für S, die jede Unterklasse implementieren muss.
Wenn Sie dies tun, kann die "foo" -Methode auch ihre Signatur in Foo (S o) ändern, wodurch sie typsicher wird, und Sie müssen diese hässliche Ausnahme nicht auslösen.
quelle
Sie können den Mustervergleich in C # 7 oder höher verwenden:
quelle
Sie sollten Ihre Methode wirklich überladen und nicht versuchen, die Begriffsklärung selbst vorzunehmen. Die meisten Antworten berücksichtigen bisher keine zukünftigen Unterklassen, was später zu wirklich schrecklichen Wartungsproblemen führen kann.
quelle
Wenn Sie C # 4 verwenden, können Sie die neue dynamische Funktionalität nutzen, um eine interessante Alternative zu erzielen. Ich sage nicht, dass dies besser ist, tatsächlich scheint es sehr wahrscheinlich, dass es langsamer sein würde, aber es hat eine gewisse Eleganz.
Und die Verwendung:
Der Grund dafür ist, dass bei einem dynamischen C # 4-Methodenaufruf die Überladungen zur Laufzeit und nicht zur Kompilierungszeit behoben werden. Ich habe vor kurzem etwas mehr über diese Idee geschrieben . Ich möchte noch einmal wiederholen, dass dies wahrscheinlich schlechter abschneidet als alle anderen Vorschläge. Ich biete es einfach als Kuriosität an.
quelle
Ja, dank C # 7 kann dies erreicht werden. So geht's (mit Ausdrucksmuster ):
quelle
Für integrierte Typen können Sie die TypeCode-Enumeration verwenden. Bitte beachten Sie, dass GetType () etwas langsam ist, aber in den meisten Situationen wahrscheinlich nicht relevant.
Für benutzerdefinierte Typen können Sie eine eigene Aufzählung erstellen und entweder eine Schnittstelle oder eine Basisklasse mit abstrakter Eigenschaft oder Methode ...
Abstrakte Klassenimplementierung von Eigentum
Abstrakte Klassenimplementierung der Methode
Schnittstellenimplementierung von Eigenschaften
Schnittstellenimplementierung der Methode
Einer meiner Mitarbeiter hat mir auch gerade davon erzählt: Dies hat den Vorteil, dass Sie es für buchstäblich jede Art von Objekt verwenden können, nicht nur für diejenigen, die Sie definieren. Es hat den Nachteil, etwas größer und langsamer zu sein.
Definieren Sie zunächst eine statische Klasse wie folgt:
Und dann können Sie es so verwenden:
quelle
Ich mochte Virtlinks Verwendung der impliziten Eingabe , um den Schalter besser lesbar zu machen, aber ich mochte nicht, dass ein Early-Out nicht möglich ist und dass wir Zuweisungen vornehmen. Lassen Sie uns den Perf ein wenig aufdrehen.
Nun, das macht meine Finger weh. Lass es uns in T4 machen:
Das Beispiel von Virtlink ein wenig anpassen:
Lesbar und schnell. Nun, wie jeder in seinen Antworten immer wieder betont und die Art dieser Frage berücksichtigt, ist die Reihenfolge bei der Typübereinstimmung wichtig. Deshalb:
quelle
Angesichts der Tatsache, dass die Vererbung es ermöglicht, ein Objekt als mehr als einen Typ zu erkennen, kann ein Wechsel meiner Meinung nach zu einer schlechten Mehrdeutigkeit führen. Zum Beispiel:
Fall 1
Fall 2
Weil s eine Zeichenfolge und ein Objekt ist. Ich denke, wenn du ein schreibst
switch(foo)
, erwartest du, dass foo zu einem und nur einem der passtcase
Aussagen . Bei einem Einschalttyp kann die Reihenfolge, in der Sie Ihre case-Anweisungen schreiben, möglicherweise das Ergebnis der gesamten switch-Anweisung ändern. Ich denke das wäre falsch.Sie können sich eine Compilerprüfung der Typen einer "typeswitch" -Anweisung vorstellen, bei der überprüft wird, ob die aufgezählten Typen nicht voneinander erben. Das gibt es aber nicht.
foo is T
ist nicht dasselbe wiefoo.GetType() == typeof(T)
!!quelle
Ich würde auch
quelle
Eine andere Möglichkeit wäre, ein Schnittstellen-IThing zu definieren und es dann in beiden Klassen zu implementieren. Hier ist das Snipet:
quelle
Gemäß der C # 7.0-Spezifikation können Sie eine lokale Variable mit einem Gültigkeitsbereich
case
von a deklarierenswitch
:Dies ist der beste Weg, um so etwas zu tun, da es sich nur um Casting- und Push-on-the-Stack-Operationen handelt. Dies sind die schnellsten Operationen, die ein Interpreter unmittelbar nach bitweisen Operationen und
boolean
Bedingungen ausführen kann.Vergleichen Sie dies mit a
Dictionary<K, V>
ist hier viel weniger Speicher belegt: Das Halten eines Wörterbuchs erfordert mehr Speicherplatz im RAM und einige Berechnungen mehr von der CPU, um zwei Arrays (eines für Schlüssel und das andere für Werte) zu erstellen und Hash-Codes für die zu platzierenden Schlüssel zu sammeln Werte zu ihren jeweiligen Schlüsseln.Soweit ich weiß, glaube ich nicht, dass es einen schnelleren Weg geben könnte, wenn Sie nicht nur einen
if
-then
-else
Block mit demis
Operator wie folgt verwenden möchten :quelle
Sie können überladene Methoden erstellen:
Und wandeln Sie das Argument in
dynamic
type um, um die statische Typprüfung zu umgehen:quelle
Sie suchen nach
Discriminated Unions
einer Sprachfunktion von F #, aber Sie können einen ähnlichen Effekt erzielen, indem Sie eine von mir erstellte Bibliothek namens OneOf verwendenhttps://github.com/mcintyre321/OneOf
Der Hauptvorteil gegenüber
switch
(undif
undexceptions as control flow
) besteht darin, dass es zur Kompilierungszeit sicher ist - es gibt keinen Standardhandler oder DurchfallWenn Sie ein drittes Element zu o hinzufügen, wird ein Compilerfehler angezeigt, da Sie im Switch-Aufruf einen Handler Func hinzufügen müssen.
Sie können auch a
.Match
ausführen, das einen Wert zurückgibt, anstatt eine Anweisung auszuführen:quelle
Erstellen Sie eine Schnittstelle
IFooable
und lassen Sie IhreA
undB
Klassen eine gemeinsame Methode implementieren, die wiederum die entsprechende Methode aufruft, die Sie möchten:Beachten Sie, dass es besser ist,
as
zuerst zu prüfenis
und dann zu wirken, da Sie auf diese Weise 2 Würfe machen, was teurer ist.quelle
In solchen Fällen erhalte ich normalerweise eine Liste mit Prädikaten und Aktionen. Etwas in diese Richtung:
quelle
Nachdem ich die Optionen verglichen hatte, die hier einige Antworten auf F # -Funktionen gegeben hatten, stellte ich fest, dass F # eine bessere Unterstützung für typbasiertes Umschalten bietet (obwohl ich mich immer noch an C # halte).
Vielleicht möchten Sie hier und hier sehen .
quelle
C # 8-Verbesserungen des Mustervergleichs machten es möglich, dies so zu tun. In einigen Fällen erledigt es die Arbeit und prägnanter.
quelle
Ich würde eine Schnittstelle mit jedem Namen und Methodennamen erstellen, der für Ihren Switch sinnvoll wäre. Nennen wir sie jeweils:
IDoable
das sagt, dass sie implementiert werden sollenvoid Do()
.und ändern Sie die Methode wie folgt:
Zumindest sind Sie damit zur Kompilierungszeit sicher und ich vermute, dass es in Bezug auf die Leistung besser ist, als den Typ zur Laufzeit zu überprüfen.
quelle
Ich stimme Jon darin zu, dass es eine Reihe von Aktionen für den Klassennamen gibt. Wenn Sie Ihr Muster beibehalten, sollten Sie stattdessen das Konstrukt "as" verwenden:
Der Unterschied besteht darin, dass wenn Sie das Muster verwenden, wenn (foo ist Bar) {((Bar) foo) .Action (); } Du machst das Typ Casting zweimal. Jetzt wird der Compiler vielleicht optimieren und diese Arbeit nur einmal ausführen - aber ich würde mich nicht darauf verlassen.
quelle
Wie Pablo vorschlägt, ist der Schnittstellenansatz fast immer das Richtige, um damit umzugehen. Um Switch wirklich nutzen zu können, besteht eine andere Alternative darin, eine benutzerdefinierte Aufzählung zu haben, die Ihren Typ in Ihren Klassen angibt.
Dies ist auch in BCL implementiert. Ein Beispiel ist MemberInfo.MemberTypes , ein anderes
GetTypeCode
für primitive Typen wie:quelle
Dies ist eine alternative Antwort, die Beiträge von JaredPar- und VirtLink-Antworten mit den folgenden Einschränkungen mischt:
Verwendungszweck:
Code:
quelle
Ja - verwenden Sie einfach den etwas seltsam benannten "Pattern Matching" ab C # 7, um eine Übereinstimmung mit Klasse oder Struktur zu erzielen:
quelle
ich benutze
quelle
Sollte mit funktionieren
mögen:
quelle
Wenn Sie die Klasse kennen, die Sie erwarten, aber noch kein Objekt haben, können Sie dies sogar tun:
quelle
Ab C # 8 können Sie den neuen Schalter noch präziser gestalten. Und mit der Discard-Option _ können Sie vermeiden, dass unnötige Variablen erstellt werden, wenn Sie diese nicht benötigen:
Rechnung und Versandliste sind Klassen und Dokument ist ein Objekt, das eine von beiden sein kann.
quelle