Ich habe eine switch
Struktur, die mehrere Fälle zu behandeln hat. Das switch
funktioniert über ein, enum
das die Ausgabe des doppelten Codes durch kombinierte Werte aufwirft:
// All possible combinations of One - Eight.
public enum ExampleEnum {
One,
Two, TwoOne,
Three, ThreeOne, ThreeTwo, ThreeOneTwo,
Four, FourOne, FourTwo, FourThree, FourOneTwo, FourOneThree,
FourTwoThree, FourOneTwoThree
// ETC.
}
Derzeit behandelt die switch
Struktur jeden Wert separat:
// All possible combinations of One - Eight.
switch (enumValue) {
case One: DrawOne; break;
case Two: DrawTwo; break;
case TwoOne:
DrawOne;
DrawTwo;
break;
case Three: DrawThree; break;
...
}
Sie bekommen die Idee dort. Ich habe dies derzeit in eine gestapelte if
Struktur unterteilt, um stattdessen Kombinationen mit einer einzelnen Zeile zu behandeln:
// All possible combinations of One - Eight.
if (One || TwoOne || ThreeOne || ThreeOneTwo)
DrawOne;
if (Two || TwoOne || ThreeTwo || ThreeOneTwo)
DrawTwo;
if (Three || ThreeOne || ThreeTwo || ThreeOneTwo)
DrawThree;
Dies wirft das Problem von unglaublich langen logischen Auswertungen auf, die verwirrend zu lesen und schwierig zu warten sind. Nachdem ich dies überarbeitet hatte, begann ich über Alternativen nachzudenken und über die Idee einer switch
Struktur mit Fall-Through-zwischen-Fällen nachzudenken .
Ich muss goto
in diesem Fall ein verwenden, da C#
es kein Durchfallen zulässt. Es verhindert jedoch die unglaublich langen logischen Ketten, obwohl es in der switch
Struktur herumspringt, und bringt immer noch Code-Duplizierung mit sich.
switch (enumVal) {
case ThreeOneTwo: DrawThree; goto case TwoOne;
case ThreeTwo: DrawThree; goto case Two;
case ThreeOne: DrawThree; goto default;
case TwoOne: DrawTwo; goto default;
case Two: DrawTwo; break;
default: DrawOne; break;
}
Dies ist immer noch keine saubere Lösung und es gibt ein Stigma im Zusammenhang mit dem goto
Keyword, das ich vermeiden möchte. Ich bin mir sicher, dass es einen besseren Weg geben muss, das aufzuräumen.
Meine Frage
Gibt es eine bessere Möglichkeit, diesen speziellen Fall zu behandeln, ohne die Lesbarkeit und Wartbarkeit zu beeinträchtigen?
quelle
goto
wenn die übergeordnete Struktur in Ihrer Sprache nicht vorhanden ist. Ich wünschte manchmal, es gäbe einefallthru
; Stichwort, um diese besondere Verwendung von loszuwerden,goto
aber na ja.goto
Sie wahrscheinlich viele andere (und bessere) Entwurfs- und / oder Implementierungsalternativen übersehen , wenn Sie das Bedürfnis haben, a in einer Hochsprache wie C # zu verwenden. Sehr entmutigt.Antworten:
Ich finde den Code mit den
goto
Aussagen schwer zu lesen . Ich würde empfehlen, Ihreenum
anders zu strukturieren . Wenn es sich bei Ihrem Beispielenum
um ein Bitfeld handelt, in dem jedes Bit eine der Auswahlmöglichkeiten darstellt, könnte dies folgendermaßen aussehen:Das Attribut Flags teilt dem Compiler mit, dass Sie Werte einrichten, die sich nicht überlappen. Der Code, der diesen Code aufruft, könnte das entsprechende Bit setzen. Sie könnten dann so etwas tun, um zu verdeutlichen, was passiert:
Dies erfordert den Code,
myEnum
der eingerichtet wird, um die Bitfelder ordnungsgemäß festzulegen und mit dem Attribut Flags zu kennzeichnen. Sie können dies jedoch tun, indem Sie die Werte der Aufzählungen in Ihrem Beispiel wie folgt ändern:Wenn Sie eine Zahl in das Formular schreiben
0bxxxx
, geben Sie diese binär an. Sie können also sehen, dass wir Bit 1, 2 oder 3 setzen (technisch gesehen 0, 1 oder 2, aber Sie haben die Idee). Sie können Kombinationen auch mit einem bitweisen ODER benennen, wenn die Kombinationen häufig zusammengesetzt werden.quelle
public enum ExampleEnum { One = 1 << 0, Two = 1 << 1, Three = 1 << 2, OneAndTwo = One | Two, OneAndThree = One | Three, TwoAndThree = Two | Three };
. Sie müssen nicht auf C # 7 + bestehen.[Flags]
Attribut signalisiert dem Compiler nichts. Aus diesem Grund müssen Sie die Aufzählungswerte immer noch explizit als Potenzen von 2 deklarieren.HasFlag
ist ein beschreibender und expliziter Begriff für die auszuführende Operation und abstrahiert die Implementierung von der Funktionalität. Die Verwendung von,&
weil sie in anderen Sprachen üblich ist, ist nicht sinnvoller als die Verwendung einesbyte
Typs anstelle der Deklaration vonenum
.IMO ist die Wurzel des Problems, dass dieses Stück Code nicht einmal existieren sollte.
Anscheinend müssen Sie drei unabhängige Bedingungen und drei unabhängige Aktionen ausführen, wenn diese Bedingungen erfüllt sind. Warum wird all das in einem Code zusammengefasst, der drei Boolesche Flags benötigt, um zu bestimmen, was zu tun ist (ob Sie sie in eine Aufzählung verschleiern oder nicht), und führt dann eine Kombination aus drei unabhängigen Dingen aus? Das Prinzip der Einzelverantwortung scheint hier einen Ruhetag zu haben.
Rufen Sie die drei Funktionen auf, zu denen sie gehören (dh, Sie stellen fest, dass die Aktionen ausgeführt werden müssen), und geben Sie den Code in diesen Beispielen in den Papierkorb.
Wenn es zehn Flags und keine drei Aktionen gäbe, würden Sie diese Art von Code erweitern, um 1024 verschiedene Kombinationen zu verarbeiten? Ich hoffe nicht! Wenn 1024 zu viel ist, ist 8 aus dem gleichen Grund auch zu viel.
quelle
Niemals gotos benutzen ist eine der "Lügen an Kinder" -Konzepte der Informatik. Es ist in 99% der Fälle der richtige Ratschlag, und die Zeiten, in denen es nicht so selten und spezialisiert ist, sind weitaus besser für alle, wenn es nur neuen Programmierern als "Verwenden Sie sie nicht" erklärt wird.
Wann sollten sie verwendet werden? Es gibt einige Szenarien , aber die grundlegende, die Sie zu treffen scheinen, ist: Wenn Sie eine Zustandsmaschine codieren . Wenn es keinen besser organisierten und strukturierten Ausdruck Ihres Algorithmus gibt als einen Zustandsautomaten, dann beinhaltet sein natürlicher Ausdruck im Code unstrukturierte Verzweigungen, und es kann nicht viel getan werden, um die Struktur des Algorithmus nicht zu beeinflussen Zustandsmaschine selbst eher undurchsichtig als weniger.
Compiler-Autoren wissen dies, weshalb der Quellcode für die meisten Compiler, die LALR-Parser * implementieren, gotos enthält. Allerdings werden nur sehr wenige Menschen jemals ihre eigenen lexikalischen Analysatoren und Parser codieren.
* - IIRC, es ist möglich, LALL-Grammatiken vollständig rekursiv zu implementieren, ohne auf Sprungtabellen oder andere unstrukturierte Steueranweisungen zurückzugreifen. Wenn Sie also wirklich gegen Goto sind, ist dies ein Ausweg.
Nun lautet die nächste Frage: "Ist dieses Beispiel einer dieser Fälle?"
Was ich sehe, ist, dass Sie drei verschiedene mögliche nächste Zustände haben, abhängig von der Verarbeitung des aktuellen Zustands. Da einer von ihnen ("default") nur eine Codezeile ist, können Sie ihn technisch entfernen, indem Sie diese Codezeile am Ende der Zustände anheften, für die er gilt. Das würde Sie auf 2 mögliche nächste Zustände bringen.
Einer der verbleibenden ("Drei") ist nur von einer Stelle abgezweigt, die ich sehen kann. So können Sie es auf die gleiche Weise loswerden. Am Ende würden Sie Code haben, der so aussieht:
Dies war jedoch wieder ein Spielzeugbeispiel, das Sie zur Verfügung gestellt haben. In Fällen, in denen "default" eine nicht triviale Menge an Code enthält, wird "three" aus mehreren Zuständen übernommen, oder (was am wichtigsten ist) eine weitere Wartung wird wahrscheinlich die Zustände ergänzen oder komplizieren , sind Sie ehrlich gesagt besser dran Verwenden von gotos (und vielleicht sogar die Enum-Case-Struktur loswerden, die die Zustandsmaschinen-Natur der Dinge verbirgt, es sei denn, es gibt einen guten Grund, warum es bleiben muss).
quelle
goto
.Die beste Antwort ist Polymorphismus .
Eine andere Antwort, die, IMO, das Wenn-Zeug klarer und wohl kürzer macht :
goto
ist wahrscheinlich meine 58. Wahl hier ...quelle
Warum nicht das:
OK, ich stimme zu, das ist hackisch (ich bin übrigens kein C # -Entwickler, entschuldigen Sie mich für den Code), aber aus Sicht der Effizienz ist dies ein Muss? Die Verwendung von Enums als Array-Index ist in C # zulässig.
quelle
int[ExempleEnum.length] COUNTS = { 1, 3, 4, 2, 5, 3 };
?Wenn Sie keine Flags verwenden können oder möchten, verwenden Sie eine rekursive Schwanzfunktion. Im 64-Bit-Release-Modus erzeugt der Compiler Code, der Ihrer
goto
Anweisung sehr ähnlich ist . Du musst dich einfach nicht damit auseinandersetzen.quelle
Die akzeptierte Lösung ist in Ordnung und eine konkrete Lösung für Ihr Problem. Ich möchte jedoch eine alternative, abstraktere Lösung vorschlagen.
Nach meiner Erfahrung ist die Verwendung von Aufzählungen zur Definition des Logikflusses ein Codegeruch, da dies oft ein Zeichen für ein schlechtes Klassendesign ist.
Ich bin auf ein Beispiel aus der Praxis gestoßen, das in Code vorkommt, an dem ich letztes Jahr gearbeitet habe. Der ursprüngliche Entwickler hatte eine einzelne Klasse erstellt, die sowohl Import- als auch Exportlogik ausführte, und anhand einer Aufzählung zwischen den beiden Klassen gewechselt. Jetzt war der Code ähnlich und hatte einige doppelte Codes, aber es war anders genug, dass dies das Lesen des Codes erheblich erschwerte und das Testen praktisch unmöglich machte. Am Ende habe ich das in zwei separate Klassen umgestaltet, die beide Klassen vereinfachten und es mir ermöglichten, eine Reihe von nicht gemeldeten Fehlern zu erkennen und zu beseitigen.
Ich muss noch einmal feststellen, dass die Verwendung von Aufzählungen zur Steuerung des Logikflusses häufig ein Entwurfsproblem darstellt. Im Allgemeinen sollten Enums hauptsächlich verwendet werden, um typsichere, verbraucherfreundliche Werte bereitzustellen, bei denen die möglichen Werte klar definiert sind. Sie werden besser als Eigenschaft (z. B. als Spalten-ID in einer Tabelle) als als Logiksteuerungsmechanismus verwendet.
Betrachten wir das in der Frage dargestellte Problem. Ich kenne den Kontext hier nicht wirklich, oder was diese Aufzählung darstellt. Zeichnet es Karten? Bilder malen? Blut abnehmen? Ist Ordnung wichtig? Ich weiß auch nicht, wie wichtig Leistung ist. Wenn Leistung oder Arbeitsspeicher kritisch sind, ist diese Lösung wahrscheinlich nicht die gewünschte.
Betrachten wir auf jeden Fall die Aufzählung:
Was wir hier haben, sind eine Reihe von verschiedenen Aufzählungswerten, die unterschiedliche Geschäftskonzepte darstellen.
Wir könnten stattdessen Abstraktionen verwenden, um die Dinge zu vereinfachen.
Betrachten wir die folgende Schnittstelle:
Wir können dies dann als abstrakte Klasse implementieren:
Wir können eine konkrete Klasse haben, um Zeichnung eins, zwei und drei darzustellen (die aus Gründen der Argumentation eine andere Logik haben). Diese könnten möglicherweise die oben definierte Basisklasse verwenden, aber ich gehe davon aus, dass sich das DrawOne-Konzept von dem durch die Aufzählung dargestellten Konzept unterscheidet:
Und jetzt haben wir drei separate Klassen, die zusammengesetzt werden können, um die Logik für die anderen Klassen bereitzustellen.
Dieser Ansatz ist weitaus ausführlicher. Aber es hat Vorteile.
Betrachten Sie die folgende Klasse, die einen Fehler enthält:
Wie viel einfacher ist es, den fehlenden Aufruf von drawThree.Draw () zu erkennen? Und wenn die Reihenfolge wichtig ist, ist die Reihenfolge der Draw Calls auch sehr einfach zu sehen und zu befolgen.
Nachteile dieses Ansatzes:
Vorteile dieses Ansatzes:
Ziehen Sie diesen (oder einen ähnlichen) Ansatz in Betracht, wenn Sie das Gefühl haben, komplexen Logiksteuercode in case-Anweisungen schreiben zu müssen. Zukünftig wirst du froh sein, dass du es getan hast.
quelle
Wenn Sie beabsichtigen, hier einen Schalter zu verwenden, ist Ihr Code tatsächlich schneller, wenn Sie jeden Fall separat behandeln
es wird jeweils nur eine einzige Rechenoperation durchgeführt
auch wie andere angegeben haben, sollten Sie, wenn Sie überlegen, gotos zu verwenden, wahrscheinlich Ihren Algorithmus überdenken (obwohl ich zugeben werde, dass C # mangelnde Groß- und Kleinschreibung ein Grund sein könnte, goto zu verwenden). Siehe Edgar Dijkstra berühmte Zeitung "Go To Statement als schädlich"
quelle
Da für Ihr spezielles Beispiel von der Aufzählung nur ein Do / Do-Indikator für jeden der Schritte gewünscht wurde, ist die Lösung, die Ihre drei
if
Anweisungen umschreibt , einer vorzuziehenswitch
, und es ist gut, dass Sie sie zur akzeptierten Antwort gemacht haben .Aber wenn Sie eine komplexere Logik hatten, die nicht so sauber funktioniert hat, dann finde ich das
goto
s in derswitch
Anweisung immer noch verwirrend. Ich würde eher so etwas sehen:Das ist nicht perfekt, aber ich denke, es ist besser so als mit dem
goto
s. Wenn die Sequenzen von Ereignissen so lang sind und sich so stark duplizieren, dass es wirklich keinen Sinn macht, die vollständige Sequenz für jeden Fall zu buchstabieren, bevorzuge ich eine Subroutinegoto
, um die Doppelung von Code zu reduzieren.quelle
Der Grund, das
goto
Schlüsselwort zu hassen, ist Code wieHoppla! Das wird natürlich nicht funktionieren. Die
message
Variable ist in diesem Codepfad nicht definiert. Also wird C # das nicht bestehen. Aber es könnte implizit sein. ErwägenUnd davon ausgehen, dass
display
dann dieWriteLine
Aussage drin ist.Diese Art von Fehler kann schwer zu finden sein, da der
goto
Codepfad verdeckt wird.Dies ist ein vereinfachtes Beispiel. Angenommen, ein reales Beispiel wäre bei weitem nicht so offensichtlich. Zwischen
label:
und der Verwendung von können fünfzig Codezeilen liegenmessage
.Die Sprache kann Abhilfe schaffen, indem sie die Verwendungsmöglichkeiten einschränkt
goto
und nur aus Blöcken heraus absteigt. Aber das C #goto
ist nicht so begrenzt. Es kann über Code springen. Wenn Sie einschränken möchtengoto
, ist es außerdem gut, den Namen zu ändern. Andere Sprachenbreak
werden zum Verlassen von Blöcken verwendet, entweder mit einer Nummer (Anzahl der zu beendenden Blöcke) oder einer Bezeichnung (auf einem der Blöcke).Das Konzept von
goto
ist eine Maschinensprachenanweisung auf niedriger Ebene. Der ganze Grund, warum wir über höhere Sprachen verfügen, ist, dass wir uns auf die höheren Abstraktionen beschränken, z. B. den variablen Umfang.Alles in allem, wenn Sie das C #
goto
in einerswitch
Anweisung verwenden, um von Fall zu Fall zu springen, ist dies einigermaßen harmlos. Jeder Fall ist bereits ein Einstiegspunkt. Ich denke immer noch, dass esgoto
dumm ist , es in dieser Situation zu nennen, da es diesen harmlosen Gebrauchgoto
mit gefährlicheren Formen in Verbindung bringt . Mir wäre es viel lieber, wenn sie so etwas benutzen würdencontinue
. Aber aus irgendeinem Grund haben sie vergessen, mich zu fragen, bevor sie die Sprache geschrieben haben.quelle
C#
Sprache können Sie nicht über Variablendeklarationen springen. Starten Sie zum Prüfen eine Konsolenanwendung und geben Sie den Code ein, den Sie in Ihrem Beitrag angegeben haben. In Ihrem Etikett wird ein Compilerfehler angezeigt, der besagt, dass der Fehler durch die Verwendung der nicht zugewiesenen lokalen Variablen "message" verursacht wird . In Sprachen, in denen dies zulässig ist, ist dies ein berechtigtes Problem, jedoch nicht in der C # -Sprache.Wenn Sie so viele Möglichkeiten haben (und, wie Sie sagen, noch mehr), dann ist es vielleicht kein Code, sondern Daten.
Erstellen Sie ein Wörterbuch, in dem die Aufzählungswerte den Aktionen zugeordnet werden, entweder als Funktionen oder als einfacher Aufzählungstyp, der die Aktionen darstellt. Dann kann Ihr Code zu einem einfachen Nachschlagen des Wörterbuchs zusammengefasst werden, gefolgt vom Aufrufen des Funktionswerts oder dem Einschalten der vereinfachten Auswahlmöglichkeiten.
quelle
Verwenden Sie eine Schleife und den Unterschied zwischen break und continue.
quelle