C # switch statement Einschränkungen - warum?
Beim schreiben einer switch-Anweisung, es scheint sich um zwei Beschränkungen auf, was kann man in case-Anweisungen.
Zum Beispiel (und ja, ich weiß, wenn du tust diese Art der Sache bedeutet es vermutlich, dass Ihre Objekt-orientierte (OO) - Architektur ist zweifelhaft - das ist nur ein erfundenes Beispiel!),
Type t = typeof(int);
switch (t) {
case typeof(int):
Console.WriteLine("int!");
break;
case typeof(string):
Console.WriteLine("string!");
break;
default:
Console.WriteLine("unknown!");
break;
}
Hier die switch () - Anweisung nicht mit 'Einem Wert eines ganzzahligen Typ erwartet' und die case-Anweisungen fehl mit 'konstanter Wert erwartet wird'.
Warum sind diese Einschränkungen im Ort, und was ist die zugrunde liegende Rechtfertigung? Ich sehe keinen Grund, warum die switch-Anweisung hat zu erliegen, um die statische Analyse nur, warum der Wert wird eingeschaltet hat, um integral zu sein, (das ist, primitiv). Was ist die Rechtfertigung?
InformationsquelleAutor der Frage ljs | 2008-09-04
Du musst angemeldet sein, um einen Kommentar abzugeben.
Dies ist mein original post, was eine Diskussion entfachte... weil es falsch ist:
In der Tat, die C# - switch-Anweisung ist nicht immer eine Konstante Zeit-Zweig.
In einigen Fällen wird der compiler verwenden eine CIL-Anweisung switch, die ist in der Tat eine Konstante Zeit-Zweig mit einer Sprung-Tabelle. Aber in spärlichen Fällen, wie bereits von Ivan Hamilton der compiler erzeugt möglicherweise etwas ganz anderes.
Dies ist eigentlich ganz einfach zu überprüfen, durch das schreiben von verschiedenen C# - switch-Anweisungen, die einige spärliche, Teils dichten, und betrachten der resultierenden CIL mit dem ildasm.exe tool.
InformationsquelleAutor der Antwort Brian Ensink
Ist es wichtig, nicht zu verwechseln Sie die C# - switch-Anweisung mit der CIL switch-Anweisung.
Den CIL-switch ist eine jump-Tabelle muss ein index in einer Reihe von Sprung-Adressen.
Dies ist nur sinnvoll, wenn die C# - switch-Fällen angrenzen:
Aber wenig, wenn Sie nicht sind:
(Sie müssten eine Tabelle ~3000 Einträge in der Größe, mit nur 3 slots verwendet)
Mit nicht-benachbarte Ausdrücke, die der compiler beginnen kann, um das durchführen der linearen if-else-if-else Kontrollen.
Mit größeren nicht - benachbarte expression legt der compiler kann mit einem binären Baum suchen, und schließlich, wenn-else-if-else die letzten Elemente.
Mit der expression legt mit Klumpen von benachbarten Elemente, der compiler kann binärer Baum die Suche, und schließlich wird ein CIL-Schalter.
Dieses ist voll von "mays" & "Mächten", und es ist abhängig vom compiler (unterscheiden können mit Mono-oder Rotor).
Ich repliziert Ihre Ergebnisse auf meiner Maschine mit angrenzenden Fällen:
Dann habe ich auch mit non-adjacent Fall Ausdrücken:
Was lustig ist hier, dass der binäre Baum suchen, erscheint ein wenig (wahrscheinlich nicht statistisch) schneller als der CIL switch-Anweisung.
Brian, du hast das Wort "Konstante", das hat eine sehr konkrete Bedeutung von einem Komplexitätstheorie Perspektive. Während die vereinfachende angrenzenden integer Beispiel kann produzieren CIL, ist als O(1) (Konstante), einen lichten Beispiel ist O(log n) (logarithmisch), Cluster Beispiele liegen irgendwo dazwischen, und kleine Beispiele sind O(n) (linear).
Diese gar nicht Adresse die Zeichenfolge situation, in der eine statische
Generic.Dictionary<string,int32>
erstellt werden können, und leiden bestimmte overhead bei der ersten Verwendung. Leistung hier wird abhängig von der Leistung derGeneric.Dictionary
.Wenn Sie das Kontrollkästchen C# - Programmiersprachenspezifikation (nicht im CIL-spec)
finden Sie "15.7.2 Die switch-Anweisung" macht keine Erwähnung von "Konstante Zeit", oder dass die zugrunde liegenden Implementierung verwendet sogar den CIL switch-Anweisung (sehr vorsichtig sein, anzunehmen, solche Dinge).
Am Ende des Tages, eine C# - switch gegen ein ganzzahliger Ausdruck, der auf ein modernes system ist ein sub-Mikrosekunden-Betrieb, und normalerweise keine sorgen machen.
Natürlich diesen Zeiten wird davon abhängen, Maschinen und Bedingungen. Ich würde nicht bezahlen, die Aufmerksamkeit auf diese timing-tests, die Mikrosekunde Dauer, über die wir sprechen, sind in den Schatten gestellt von einem "echten" code ausgeführt wird (und Sie müssen einige "echten code", sonst kann der compiler optimieren den Zweig entfernt), oder jitter im system. Meine Antworten basieren auf der Verwendung von IL DASM zu prüfen, die CIL, erstellt von der C# - compiler. Natürlich, dies ist nicht endgültig, da die tatsächlichen Anweisungen, die der CPU ausgeführt werden, werden dann erstellt, indem der JIT.
Ich habe die letzten CPU-Befehle tatsächlich ausgeführt wird, auf meinem x86-Rechner, und kann bestätigen, eine einfache benachbarten Schalter etwas wie:
Wo ein binärer Baum die Suche ist voll von:
InformationsquelleAutor der Antwort Ivan Hamilton
Der erste Grund, der mir einfällt, ist historischen:
Da die meisten C -, C++ - und Java-Programmierer sind nicht daran gewöhnt, dass solche Freiheiten, Sie machen nicht verlangen Sie.
Anderen, wichtigeren, Grund dafür ist, dass die Sprache, die die Komplexität erhöhen würde:
Zunächst, sollten die Objekte, die verglichen werden mit
.Equals()
oder mit der==
Betreiber? Beide sind in einigen Fällen gilt. Sollten wir neue syntax zu tun? Sollten wir zulassen, dass die Programmierer stellen Ihre eigenen Vergleichs-Methode?Zusätzlich, so dass zum einschalten der Objekte würde Pause zu Grunde liegenden Annahmen über die switch-Anweisung. Es gibt zwei Regeln, die für die switch-Anweisung, die der compiler nicht in der Lage wäre, zu erzwingen, wenn Objekte durften eingeschaltet werden (siehe C# language specification version 3.0§8.7.2):
Betrachten Sie dieses code-Beispiel in dem hypothetischen Fall, dass nicht-Konstante Werte erlaubt waren:
Was der code tun? Was ist, wenn die case-Anweisungen werden neu geordnet? In der Tat, einer der Gründe, warum C# gemacht-switch fall-through illegal ist, ist, dass die switch-Anweisungen werden könnte beliebig umgeordnet werden.
Diese Regeln sind in Ort für einen Grund - damit kann der Programmierer, mit Blick auf eine "case" - block, für bestimmte wissen, die genaue Bedingung, unter der der block eingegeben wird. Wenn die oben genannten switch-Anweisung wächst in 100 Zeilen oder mehr (und das wird es), solches wissen ist von unschätzbarem Wert.
InformationsquelleAutor der Antwort Antti Sykäri
Meist, diese Einschränkungen sind in Platz wegen der Sprache-Designer. Die zugrunde liegende Begründung kann die Kompatibilität mit Sprache, Geschichte, ideale, oder die Vereinfachung der compiler design.
Der compiler kann (und tut) zu wählen:
Generisches.Wörterbuch<>::TryGetValue()
für einen index zu übergeben, um eine MSIL-Schalter
Befehl (jump table)
Kombination von if-elses & MSIL
"Schalter" springt
Die switch-Anweisung IST NICHT eine Konstante Zeit-Zweig. Der compiler finden kann short-cuts (mit hash buckets, etc.), aber kompliziertere Fälle generiert komplizierter MSIL-code mit einigen Fällen verzweigen sich früher als andere.
Behandeln die Zeichenfolge Fall, der compiler am Ende (irgendwann) mit ein.Equals(b) (und eventuell ein.GetHashCode() ). Ich denke, es wäre trival für den compiler zu verwenden, jedes Objekt erfüllt diese Bedingungen.
Als für die Notwendigkeit von statischen Fall Ausdrücken... einige dieser Optimierungen (hashing, caching, etc) würde nicht vorhanden sein, wenn die case-Ausdrücke waren nicht deterministisch. Aber wir haben bereits gesehen, dass manchmal der compiler nur die picks der simplen if-else-if-else-Weg sowieso...
Edit: lomaxx - Ihr Verständnis von "typeof" - operator nicht korrekt. Die "typeof" - operator verwendet werden, um das System.Type-Objekt für einen Typ (nichts zu tun mit seiner obertypen oder Schnittstellen). Überprüfen der run-time-Kompatibilität ein Objekt mit einem bestimmten Typ ist, der "ist" - operator job. Die Verwendung von "typeof" hier zu äußern, ein Objekt, ist irrelevant.
InformationsquelleAutor der Antwort Ivan Hamilton
Durch die Art und Weise, VB, mit der gleichen zugrunde liegenden Architektur, ermöglicht viel flexibler
Select Case
Aussagen (der obige code funktioniert in VB) und produziert noch immer effizienten code, wo dies möglich ist, so das argument, durch technische Einschränkung ist sorgfältig zu berücksichtigen.InformationsquelleAutor der Antwort Konrad Rudolph
Wahr ist, ist es nicht habenund viele Sprachen haben in der Tat die Verwendung dynamischer switch-Anweisungen. Dies bedeutet jedoch, dass die Neuordnung der "Fall" - Klauseln ändern kann das Verhalten des Codes.
Gibt es einige interessante Infos hinter den design-Entscheidungen, die ging in die "switch" - hier: Warum ist die C# - switch-Anweisung, die nicht zulassen, fallen durch, aber immer noch ein break?
Dynamischen case-Ausdrücke führen zu Monstrositäten wie diese PHP-code:
ehrlich gesagt, die sollten benutzen Sie einfach die
if-else
- Anweisung.InformationsquelleAutor der Antwort Roman Starkov
Microsoft endlich hörte Sie!
Nun mit C# 7 Sie können:
InformationsquelleAutor der Antwort dimaaan
Während auf dem Thema, so Jeff Atwood, die switch-Anweisung ist eine Programmier-atrocity. Verwenden Sie Sie sparsam.
Können Sie oft die gleiche Aufgabe mit Hilfe einer Tabelle. Zum Beispiel:
InformationsquelleAutor der Antwort Judah Himango
Dies ist kein Grund, warum, aber der C# - Spezifikation, Abschnitt 8.7.2 besagt Folgendes:
C# - 3.0-Spezifikation befindet sich unter:
http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc
InformationsquelleAutor der Antwort Mark
Juda ' s Antwort oben, gab mir eine Idee. Sie können "fake" der OP ' s-switch-Verhalten über die Verwendung einer
Dictionary<Type, Func<T>
:Diese können Sie assoziieren das Verhalten mit einer Art im gleichen Stil wie die switch-Anweisung. Ich glaube, es hat den zusätzlichen Vorteil des seins eingegeben, statt einer switch-Stil-springen Tabelle beim kompilieren zu IL.
InformationsquelleAutor der Antwort Dave Swersky
Ich nehme an, es gibt keinen fundamentalen Grund, warum der compiler konnte nicht automatisch übersetzen Ihre switch-Anweisung in:
Aber es ist nicht viel gewonnen.
Einer case-Anweisung auf integral-Typen kann der compiler eine Reihe von Optimierungen:
Es ist nicht duplizieren (es sei denn, Sie Duplikat Fall von Etiketten, die der compiler erkennt). In deinem Beispiel t könnte passen mehrere Typen durch Vererbung. Sollte die erste übereinstimmung ausgeführt werden? Alle von Ihnen?
Den compiler wählen können, zur Umsetzung einer switch-Anweisung über eine integral-Typ durch eine jump-Tabelle zu vermeiden, für alle Vergleiche. Wenn Sie wechseln auf eine Aufzählung, die integer-Werte von 0 bis 100 erstellt dann ein array mit 100-Zeiger in es, eine für jede switch-Anweisung. Zur Laufzeit sieht einfach nur die Adresse aus dem array auf der Grundlage der integer-Wert eingeschaltet wird. Dies sorgt für wesentlich bessere Laufzeit die Leistung als 100 Vergleiche.
InformationsquelleAutor der Antwort Rob Walker
Laut die switch-Anweisung-Dokumentationwenn es eine eindeutige Möglichkeit, um implizit konvertieren Sie das Objekt auf einen ganzzahligen Typ, dann wird es erlaubt sein. Ich denke, Sie erwarten ein Verhalten, bei dem sich für jede case-Anweisung würde es ersetzt werden mit
if (t == typeof(int))
aber das würde öffnen, eine ganze Dose Würmer, wenn Sie bekommen, um eine überlastung des operators. Das Verhalten würde sich ändern, wenn für die Umsetzung der details für die switch-Anweisung geändert werden, wenn Sie schrieb Ihre == überschreiben falsch. Durch die Reduzierung der Vergleiche zu integralen Typen und string-und die Dinge, die reduziert werden kann, um Integrale Typen (und sollen) Sie vermeiden mögliche Probleme.InformationsquelleAutor der Antwort fryguybob
Da die Sprache ermöglicht die string Typ verwendet werden in einer switch-Anweisung ich nehme an, der compiler ist nicht in der Lage, code zu erzeugen, der für eine Konstante Zeit-Zweig-Implementierung für diese Art und Bedürfnisse zu generieren, die eine wenn-dann-Stil.
@mweerden - Ah sehe ich. Danke.
Habe ich nicht viel Erfahrung in C# und .NET, aber es scheint die Sprache der Designer nicht erlauben, statischen Zugriff auf das Typ-system, außer in den engen Verhältnissen. Die typeof Schlüsselwort gibt ein Objekt zurück, so dass diese zugänglich ist, zur Laufzeit nur.
InformationsquelleAutor der Antwort Henk
Ich denke Henk nagelte ihn mit dem "Nein sttatic Zugriff auf das Typ-system", was
Andere Möglichkeit ist, dass es nicht um Typen, wie Zeichen und Zeichenketten werden kann. So ein Typ wechseln würde, kann nicht bauen einen binären Suchbaum, nur eine lineare Suche.
InformationsquelleAutor der Antwort BCS
Ich Stimme mit dieser Kommentardass die Verwendung einer Tabelle driven Ansatz ist oft besser.
In C# 1.0 war dies nicht möglich, weil Sie nicht über generics und anonymen Delegaten.
Neue Versionen von C# haben das Gerüst, um diese Arbeit zu machen. Mit einer notation für Objekt-Literale ist hilft auch.
InformationsquelleAutor der Antwort HS.
Ich habe praktisch keine Kenntnisse in C#, aber ich vermute, dass entweder der Schalter wurde einfach übernommen, wie es geschieht in anderen Sprachen, ohne darüber nachzudenken, dass es mehr Allgemeine oder die Entwickler beschlossen, dass eine Ausweitung der war es nicht Wert.
Streng genommen haben Sie vollkommen Recht, dass es keinen Grund gibt, diese Einschränkungen auf. Man könnte vermuten, dass der Grund ist, dass für die zulässigen Fällen die Umsetzung ist sehr effizient (wie vorgeschlagen von Brian Ensink (Vier und vierzig tausend neun hundert eins und zwanzig)), aber ich bezweifle, dass die Implementierung ist sehr effizient (w.r.t. wenn-Anweisungen), wenn ich ganze zahlen und einigen zufälligen Fällen (z.B. 345, -4574 und 1234203). Und in jedem Fall, was ist der Schaden, dass es für alles (oder zumindest mehr) und sagen, dass es ist nur effizient für bestimmte Fälle (wie (fast) aufeinanderfolgenden zahlen).
Ich kann mir jedoch vorstellen, dass man vielleicht ausschließen wollen-Typen, weil Gründe wie die eine, die von lomaxx (Vier und vierzig tausend neun hundert achtzehn).
Edit: @Henk (Vier und vierzig tausend neun hundert siebzig): Wenn Strings sind maximal freigegebene, strings mit gleichem Inhalt, wird der Zeiger auf die gleiche Speicherstelle wie gut. Dann, wenn Sie sicherstellen können, dass die verwendeten strings in den Fällen gespeichert werden hintereinander im Speicher, können Sie sehr effizient implementieren Sie den Schalter (D. H. mit der Ausführung in der Größenordnung von 2 vergleicht, eine addition und zwei Sprüngen).
InformationsquelleAutor der Antwort mweerden