Einschränkungen der C # -Schalteranweisung - warum?

140

Beim Schreiben einer switch-Anweisung scheint es zwei Einschränkungen zu geben, was Sie in case-Anweisungen aktivieren können.

Zum Beispiel (und ja, ich weiß, wenn Sie so etwas tun, bedeutet dies wahrscheinlich, dass Ihre objektorientierte (OO) Architektur fragwürdig ist - dies 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 schlägt die switch () - Anweisung mit 'Ein Wert eines erwarteten Integraltyps' fehl und die case-Anweisungen mit 'Ein konstanter Wert wird erwartet'.

Warum gibt es diese Einschränkungen und was ist die zugrunde liegende Rechtfertigung? Ich sehe keinen Grund, warum die switch-Anweisung nur einer statischen Analyse unterliegen muss und warum der eingeschaltete Wert ganzzahlig (dh primitiv) sein muss. Was ist die Rechtfertigung?

ljs
quelle
1
Mögliche Problemumgehung anzeigen Gibt es eine bessere Alternative zum Einschalten des Typs?
Michael Freidgeim
Eine weitere Option zum Einschalten integrierter Typen ist die Verwendung der TypeCode-Enumeration .
Erik Philips

Antworten:

98

Dies ist mein ursprünglicher Beitrag, der einige Debatten ausgelöst hat ... weil er falsch ist :

Die switch-Anweisung ist nicht dasselbe wie eine große if-else-Anweisung. Jeder Fall muss eindeutig sein und statisch bewertet werden. Die switch-Anweisung führt eine konstante Zeitverzweigung durch, unabhängig davon, wie viele Fälle Sie haben. Die if-else-Anweisung wertet jede Bedingung aus, bis eine zutreffende gefunden wird.


Tatsächlich ist die C # -Switch-Anweisung nicht immer ein konstanter Zeitzweig.

In einigen Fällen verwendet der Compiler eine CIL-switch-Anweisung, die in der Tat eine konstante Zeitverzweigung unter Verwendung einer Sprungtabelle ist. In spärlichen Fällen, auf die Ivan Hamilton hingewiesen hat, kann der Compiler jedoch etwas ganz anderes generieren.

Dies ist eigentlich recht einfach zu überprüfen, indem verschiedene C # -Schalteranweisungen geschrieben werden, einige spärlich, andere dicht, und die resultierende CIL mit dem Tool ildasm.exe betrachtet werden.

Brian Ensink
quelle
4
Wie in anderen Antworten (einschließlich meiner) angegeben, sind die in dieser Antwort gemachten Angaben nicht korrekt. Ich würde das Löschen empfehlen (schon allein, um dieses (wahrscheinlich häufige) Missverständnis nicht durchzusetzen).
Mweerden
Bitte lesen Sie meinen Beitrag unten, in dem ich meiner Meinung nach abschließend zeige, dass die switch-Anweisung einen konstanten Zeitzweig ausführt.
Brian Ensink
Vielen Dank für Ihre Antwort, Brian. Bitte lesen Sie die Antwort von Ivan Hamilton ((48259) [ beta.stackoverflow.com/questions/44905/#48259] ). Kurz gesagt: Sie sprechen von der switch Anweisung (der CIL), die nicht mit der switchAnweisung von C # identisch ist .
Mweerden
Ich glaube nicht, dass der Compiler beim Einschalten von Strings eine zeitlich konstante Verzweigung erzeugt.
Drew Noakes
Gilt dies weiterhin für den Mustervergleich in Switch-Case-Anweisungen in C # 7.0?
B. Darren Olson
114

Es ist wichtig, die Anweisung C # switch nicht mit der Anweisung CIL switch zu verwechseln.

Der CIL-Schalter ist eine Sprungtabelle, für die ein Index für eine Reihe von Sprungadressen erforderlich ist.

Dies ist nur nützlich, wenn die Fälle des C # -Schalters benachbart sind:

case 3: blah; break;
case 4: blah; break;
case 5: blah; break;

Aber von geringem Nutzen, wenn sie nicht:

case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;

(Sie benötigen eine Tabelle mit einer Größe von ca. 3000 Einträgen und nur 3 verwendeten Steckplätzen.)

Bei nicht benachbarten Ausdrücken kann der Compiler beginnen, lineare If-else-if-else-Prüfungen durchzuführen.

Bei größeren nicht benachbarten Ausdruckssätzen kann der Compiler mit einer binären Baumsuche und schließlich mit den letzten Elementen beginnen, wenn-sonst-wenn-sonst.

Bei Ausdruckssätzen, die Klumpen benachbarter Elemente enthalten, kann der Compiler eine binäre Baumsuche und schließlich einen CIL-Schalter durchführen.

Dies ist voll von "Mai" und "Macht" und hängt vom Compiler ab (kann bei Mono oder Rotor abweichen).

Ich habe Ihre Ergebnisse auf meinem Computer anhand benachbarter Fälle repliziert:

Gesamtzeit für die Ausführung eines 10-Wege-Schalters, 10000 Iterationen (ms): 25.1383
ungefähre Zeit pro 10-Wege-Schalter (ms): 0,00251383

Gesamtzeit für die Ausführung eines 50-Wege-Schalters, 10000 Iterationen (ms): 26.593
ungefähre Zeit pro 50-Wege-Schalter (ms): 0,0026593

Gesamtzeit für die Ausführung eines 5000-Wege-Schalters, 10000 Iterationen (ms): 23.7094
ungefähre Zeit pro 5000-Wege-Schalter (ms): 0.00237094

Gesamtzeit für die Ausführung eines 50000-Wege-Schalters, 10000 Iterationen (ms): 20.0933
ungefähre Zeit pro 50000-Wege-Schalter (ms): 0.00200933

Dann habe ich auch nicht benachbarte Fallausdrücke verwendet:

Gesamtzeit für die Ausführung eines 10-Wege-Schalters, 10000 Iterationen (ms): 19.6189
ungefähre Zeit pro 10-Wege-Schalter (ms): 0.00196189

Gesamtzeit für die Ausführung eines 500-Wege-Schalters, 10000 Iterationen (ms): 19.1664
ungefähre Zeit pro 500-Wege-Schalter (ms): 0.00191664

Gesamtzeit für die Ausführung eines 5000-Wege-Schalters, 10000 Iterationen (ms): 19,5871
ungefähre Zeit pro 5000-Wege-Schalter (ms): 0,00195871

Eine nicht benachbarte 50.000-Fall-Switch-Anweisung würde nicht kompiliert.
"Ein Ausdruck ist zu lang oder zu komplex, um in der Nähe von 'ConsoleApplication1.Program.Main (string [])' kompiliert zu werden.

Was hier lustig ist, ist, dass die binäre Baumsuche etwas (wahrscheinlich nicht statistisch) schneller erscheint als die CIL-Schalteranweisung.

Brian, Sie haben das Wort " Konstante " verwendet, das aus Sicht der rechnergestützten Komplexitätstheorie eine ganz bestimmte Bedeutung hat. Während das vereinfachte benachbarte Ganzzahlbeispiel CIL erzeugen kann, das als O (1) (konstant) betrachtet wird, ist ein spärliches Beispiel O (log n) (logarithmisch), gruppierte Beispiele liegen irgendwo dazwischen und kleine Beispiele sind O (n) (linear) ).

Dies betrifft nicht einmal die String-Situation, in der eine statische Generic.Dictionary<string,int32>Aufladung erstellt werden kann, und führt bei der ersten Verwendung zu einem deutlichen Overhead. Die Leistung hier hängt von der Leistung von ab Generic.Dictionary.

Wenn Sie die C # -Sprachenspezifikation (nicht die CIL-Spezifikation) überprüfen, finden Sie "15.7.2 Die switch-Anweisung" erwähnt "konstante Zeit" nicht oder dass die zugrunde liegende Implementierung sogar die CIL-switch-Anweisung verwendet (seien Sie sehr vorsichtig bei der Annahme solche Sachen).

Letztendlich ist ein C # -Schalter gegen einen ganzzahligen Ausdruck in einem modernen System eine Operation im Submikrosekundenbereich und normalerweise keine Sorge wert.


Natürlich hängen diese Zeiten von den Maschinen und Bedingungen ab. Ich würde diesen Timing-Tests keine Aufmerksamkeit schenken, die Mikrosekunden-Dauer, von der wir sprechen, wird durch die Ausführung von "echtem" Code in den Schatten gestellt (und Sie müssen "echten Code" einfügen, sonst optimiert der Compiler die Verzweigung) oder Jitter im System. Meine Antworten basieren auf der Verwendung von IL DASM , um die vom C # -Compiler erstellte CIL zu untersuchen. Dies ist natürlich nicht endgültig, da die tatsächlichen Anweisungen, die die CPU ausführt, dann von der JIT erstellt werden.

Ich habe die endgültigen CPU-Anweisungen überprüft, die tatsächlich auf meinem x86-Computer ausgeführt wurden, und kann einen einfachen benachbarten Set-Schalter bestätigen, der Folgendes ausführt:

  jmp     ds:300025F0[eax*4]

Wo eine binäre Baumsuche voll ist von:

  cmp     ebx, 79Eh
  jg      3000352B
  cmp     ebx, 654h
  jg      300032BB
  
  cmp     ebx, 0F82h
  jz      30005EEE
Ivan Hamilton
quelle
Die Ergebnisse Ihrer Experimente überraschen mich ein wenig. Hast du deine mit Brian's getauscht? Seine Ergebnisse zeigen eine Zunahme mit der Größe, während Ihre dies nicht tun. Mir fehlt etwas? Auf jeden Fall danke für die klare Antwort.
Mweerden
Es ist schwierig, das Timing mit einer so kleinen Operation genau zu berechnen. Wir haben keinen Code oder Testverfahren geteilt. Ich kann nicht verstehen, warum seine Zeiten für benachbarte Fälle länger werden sollten. Meine waren 10x schneller, daher können Umgebungen und Testcode stark variieren.
Ivan Hamilton
23

Der erste Grund, der mir in den Sinn kommt, ist historisch :

Da die meisten C-, C ++ - und Java-Programmierer an solche Freiheiten nicht gewöhnt sind, fordern sie diese nicht.

Ein weiterer, zutreffenderer Grund ist, dass die Sprachkomplexität zunehmen würde :

Sollten die Objekte zunächst mit .Equals()oder mit dem ==Bediener verglichen werden? Beide sind in einigen Fällen gültig. Sollten wir dazu eine neue Syntax einführen? Sollten wir dem Programmierer erlauben, seine eigene Vergleichsmethode einzuführen?

Darüber hinaus würde das Zulassen des Einschaltens von Objekten die zugrunde liegenden Annahmen über die switch-Anweisung brechen . Es gibt zwei Regeln für die switch-Anweisung, die der Compiler nicht erzwingen kann, wenn Objekte eingeschaltet werden dürfen (siehe Sprachspezifikation für C # Version 3.0 , §8.7.2):

  • Dass die Werte der Schalterbezeichnungen konstant sind
  • Dass die Werte von Schalterbezeichnungen unterschiedlich sind (so dass nur ein Schalterblock für einen bestimmten Schalterausdruck ausgewählt werden kann)

Betrachten Sie dieses Codebeispiel in dem hypothetischen Fall, dass nicht konstante Fallwerte zulässig waren:

void DoIt()
{
    String foo = "bar";
    Switch(foo, foo);
}

void Switch(String val1, String val2)
{
    switch ("bar")
    {
        // The compiler will not know that val1 and val2 are not distinct
        case val1:
            // Is this case block selected?
            break;
        case val2:
            // Or this one?
            break;
        case "bar":
            // Or perhaps this one?
            break;
    }
}

Was wird der Code tun? Was ist, wenn die case-Anweisungen neu angeordnet werden? In der Tat ist einer der Gründe, warum C # das Durchfallen von Schaltern illegal gemacht hat, dass die Schalteranweisungen willkürlich neu angeordnet werden könnten.

Diese Regeln sind aus einem bestimmten Grund vorhanden, damit der Programmierer anhand eines Fallblocks sicher wissen kann, unter welchen genauen Bedingungen der Block eingegeben wird. Wenn die oben erwähnte switch-Anweisung auf 100 Zeilen oder mehr anwächst (und dies auch tun wird), ist dieses Wissen von unschätzbarem Wert.

Antti Kissaniemi
quelle
2
Bemerkenswert bei der Neuordnung des Schalters. Fall Through ist legal, wenn der Fall keinen Code enthält. Beispiel: Fall 1: Fall 2: Console.WriteLine ("Hi"); brechen;
Joel McBeth
10

Übrigens erlaubt VB mit der gleichen zugrunde liegenden Architektur viel flexiblere Select CaseAnweisungen (der obige Code würde in VB funktionieren) und erzeugt dennoch effizienten Code, wo dies möglich ist, so dass das Argument durch technische Einschränkungen sorgfältig abgewogen werden muss.

Konrad Rudolph
quelle
1
Der Select Caseen VB ist sehr flexibel und spart viel Zeit. Ich vermisse es sehr.
Eduardo Molteni
@EduardoMolteni Dann auf F # umschalten. Dadurch wirken die Schalter von Pascal und VB im Vergleich dazu wie idiotische Kinder.
Luaan
10

Meistens bestehen diese Einschränkungen aufgrund von Sprachdesignern. Die zugrunde liegende Rechtfertigung kann die Kompatibilität mit der Sprachgeschichte, den Idealen oder die Vereinfachung des Compilerdesigns sein.

Der Compiler kann (und tut) wählen:

  • Erstellen Sie eine große if-else-Anweisung
  • Verwenden Sie eine MSIL-Schaltanweisung (Sprungtabelle).
  • Erstellen Sie ein Generic.Dictionary <string, int32>, füllen Sie es bei der ersten Verwendung aus und rufen Sie Generic.Dictionary <> :: TryGetValue () auf, damit ein Index an eine MSIL-Switch-Anweisung (Sprungtabelle) übergeben wird.
  • Verwenden Sie eine Kombination aus if-elses und MSIL-Schaltern

Die switch-Anweisung ist KEIN konstanter Zeitzweig. Der Compiler findet möglicherweise Verknüpfungen (unter Verwendung von Hash-Buckets usw.), aber kompliziertere Fälle erzeugen komplizierteren MSIL-Code, wobei einige Fälle früher verzweigen als andere.

Um den String-Fall zu behandeln, verwendet der Compiler (irgendwann) a.Equals (b) (und möglicherweise a.GetHashCode ()). Ich denke, es wäre für den Compiler trivial, jedes Objekt zu verwenden, das diese Einschränkungen erfüllt.

Was die Notwendigkeit statischer Fallausdrücke betrifft ... einige dieser Optimierungen (Hashing, Caching usw.) wären nicht verfügbar, wenn die Fallausdrücke nicht deterministisch wären. Aber wir haben bereits gesehen, dass der Compiler manchmal sowieso nur die vereinfachte Wenn-Sonst-Wenn-Sonst-Straße wählt ...

Bearbeiten: lomaxx - Ihr Verständnis des Operators "typeof" ist nicht korrekt. Der Operator "typeof" wird verwendet, um das System.Type-Objekt für einen Typ abzurufen (nichts mit seinen Supertypen oder Schnittstellen zu tun). Das Überprüfen der Laufzeitkompatibilität eines Objekts mit einem bestimmten Typ ist die Aufgabe des Operators "is". Die Verwendung von "typeof" hier, um ein Objekt auszudrücken, ist irrelevant.

Ivan Hamilton
quelle
6

Laut Jeff Atwood handelt es sich bei der switch-Anweisung um eine Programmier-Gräueltat . Verwenden Sie sie sparsam.

Sie können dieselbe Aufgabe häufig mithilfe einer Tabelle ausführen. Beispielsweise:

var table = new Dictionary<Type, string>()
{
   { typeof(int), "it's an int!" }
   { typeof(string), "it's a string!" }
};

Type someType = typeof(int);
Console.WriteLine(table[someType]);
Judah Gabriel Himango
quelle
7
Sie zitieren ernsthaft jemanden aus der Manschette Twitter-Post ohne Beweise? Zumindest Link zu einer zuverlässigen Quelle.
Ivan Hamilton
4
Es ist aus einer zuverlässigen Quelle; Der betreffende Twitter-Beitrag stammt von Jeff Atwood, dem Autor der Website, die Sie sich ansehen. :-) Jeff hat eine Handvoll Blog-Beiträge zu diesem Thema, wenn Sie neugierig sind.
Judah Gabriel Himango
Ich glaube, das ist total BS - ob Jeff Atwood es geschrieben hat oder nicht. Es ist lustig, wie gut sich die switch-Anweisung für den Umgang mit Zustandsautomaten und anderen Beispielen für die Änderung des Codeflusses basierend auf dem Wert eines enumTyps eignet . Es ist auch kein Zufall, dass Intellisense automatisch eine switch-Anweisung ausfüllt, wenn Sie eine Variable eines enumTyps einschalten .
Jonathon Reinhart
@ JonathonReinhart Ja, ich denke, das ist der Punkt - es gibt bessere Möglichkeiten, mit polymorphem Code umzugehen, als die switchAnweisung zu verwenden. Er sagt nicht, dass Sie keine Zustandsmaschinen schreiben sollten, nur dass Sie dasselbe tun können, indem Sie nette spezifische Typen verwenden. Natürlich ist dies in Sprachen wie F # viel einfacher, die Typen haben, die leicht recht komplexe Zustände abdecken können. In Ihrem Beispiel könnten Sie diskriminierte Gewerkschaften verwenden, bei denen der Status Teil des Typs wird, und die switchdurch Mustervergleich ersetzen . Oder verwenden Sie zum Beispiel Schnittstellen.
Luaan
Alte Antwort / Frage, aber ich hätte gedacht, dass (korrigieren Sie mich, wenn ich falsch liege) Dictionaryerheblich langsamer gewesen wäre als eine optimierte switchAussage ...?
Paul
6

Ich sehe keinen Grund, warum die switch-Anweisung nur der statischen Analyse entsprechen muss

Es stimmt, es nicht zu haben , und viele Sprachen verwenden dynamische switch - Anweisungen in der Tat. Dies bedeutet jedoch, dass eine Neuordnung der "case" -Klauseln das Verhalten des Codes ändern kann.

Es gibt einige interessante Informationen hinter den Entwurfsentscheidungen, die hier in "switch" eingegangen sind: Warum ist die C # -Switch-Anweisung so konzipiert, dass kein Durchfallen möglich ist, aber dennoch eine Pause erforderlich ist?

Das Zulassen dynamischer Fallausdrücke kann zu Monstrositäten wie diesem PHP-Code führen:

switch (true) {
    case a == 5:
        ...
        break;
    case b == 10:
        ...
        break;
}

was ehrlich gesagt nur die if-elseAussage verwenden sollte.

Roman Starkov
quelle
1
Das ist es, was ich an PHP liebe (jetzt, wo ich zu C # übergehe), es ist die Freiheit. Damit verbunden ist die Freiheit, schlechten Code zu schreiben, aber das vermisse ich wirklich in C #
Seidenfeuer
5

Microsoft hat dich endlich gehört!

Mit C # 7 können Sie jetzt:

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));
}
Dimaaan
quelle
3

Dies ist kein Grund dafür, aber in Abschnitt 8.7.2 der C # -Spezifikation heißt es:

Der maßgebliche Typ einer switch-Anweisung wird durch den switch-Ausdruck festgelegt. Wenn der Typ des switch-Ausdrucks sbyte, byte, short, ushort, int, uint, long, ulong, char, string oder ein Aufzählungstyp ist, ist dies der maßgebliche Typ der switch-Anweisung. Andernfalls muss genau eine benutzerdefinierte implizite Konvertierung (§6.4) vom Typ des switch-Ausdrucks in einen der folgenden möglichen maßgeblichen Typen vorhanden sein: sbyte, byte, short, ushort, int, uint, long, ulong, char, string . Wenn keine solche implizite Konvertierung vorhanden ist oder wenn mehr als eine solche implizite Konvertierung vorhanden ist, tritt ein Fehler bei der Kompilierung auf.

Die C # 3.0-Spezifikation befindet sich unter: http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc

Markus
quelle
3

Judahs Antwort oben gab mir eine Idee. Sie können das oben beschriebene Schaltverhalten des OP mit folgenden Elementen "vortäuschen" Dictionary<Type, Func<T>:

Dictionary<Type, Func<object, string,  string>> typeTable = new Dictionary<Type, Func<object, string, string>>();
typeTable.Add(typeof(int), (o, s) =>
                    {
                        return string.Format("{0}: {1}", s, o.ToString());
                    });

Auf diese Weise können Sie einem Typ Verhalten im selben Stil wie die switch-Anweisung zuordnen. Ich glaube, es hat den zusätzlichen Vorteil, dass es beim Kompilieren zu IL anstelle einer Sprungtabelle im Switch-Stil verschlüsselt wird.

Dave Swersky
quelle
0

Ich nehme an, es gibt keinen fundamentalen Grund, warum der Compiler Ihre switch-Anweisung nicht automatisch in Folgendes übersetzen konnte:

if (t == typeof(int))
{
...
}
elseif (t == typeof(string))
{
...
}
...

Aber das bringt nicht viel.

Eine case-Anweisung zu Integraltypen ermöglicht es dem Compiler, eine Reihe von Optimierungen vorzunehmen:

  1. Es gibt keine Duplizierung (es sei denn, Sie duplizieren Fallbezeichnungen, die der Compiler erkennt). In Ihrem Beispiel könnte t aufgrund der Vererbung mehreren Typen entsprechen. Sollte das erste Match ausgeführt werden? Alle von ihnen?

  2. Der Compiler kann eine switch-Anweisung über einen integralen Typ durch eine Sprungtabelle implementieren, um alle Vergleiche zu vermeiden. Wenn Sie eine Aufzählung mit ganzzahligen Werten von 0 bis 100 aktivieren, wird ein Array mit 100 Zeigern erstellt, einer für jede switch-Anweisung. Zur Laufzeit wird einfach die Adresse aus dem Array basierend auf dem eingeschalteten Ganzzahlwert nachgeschlagen. Dies führt zu einer viel besseren Laufzeitleistung als die Durchführung von 100 Vergleichen.

Rob Walker
quelle
1
Eine wichtige Komplexität, die hier zu beachten ist, ist, dass das .NET- Speichermodell bestimmte starke Garantien aufweist, die Ihren Pseudocode nicht genau mit dem (hypothetischen, ungültigen C # ) äquivalent machen, switch (t) { case typeof(int): ... }da Ihre Übersetzung impliziert, dass die Variable zweimal aus dem Speicher abgerufen werden t musst != typeof(int) , während letzteres dies tun würde (mutmaßlich) immer den Wert von t genau einmal lesen . Dieser Unterschied kann die Korrektheit von gleichzeitigem Code beeinträchtigen, der auf diesen hervorragenden Garantien beruht. Weitere Informationen hierzu finden Sie unter Joe Duffys Concurrent Programming unter Windows
Glenn Slayden,
0

Laut der Dokumentation der switch-Anweisung ist dies zulässig, wenn es eine eindeutige Möglichkeit gibt, das Objekt implizit in einen integralen Typ zu konvertieren. Ich denke, Sie erwarten ein Verhalten, bei dem für jede case-Anweisung diese ersetzt wird if (t == typeof(int)), aber das würde eine ganze Dose Würmer öffnen, wenn Sie diesen Operator überladen. Das Verhalten würde sich ändern, wenn sich die Implementierungsdetails für die switch-Anweisung ändern, wenn Sie Ihre == -Überschreibung falsch geschrieben haben. Indem die Vergleiche auf integrale Typen und Zeichenfolgen sowie auf Dinge reduziert werden, die auf integrale Typen reduziert werden können (und sollen), vermeiden sie potenzielle Probleme.

Fryguybob
quelle
0

schrieb:

"Die switch-Anweisung führt eine konstante Zeitverzweigung durch, unabhängig davon, wie viele Fälle Sie haben."

Da die Sprache die Verwendung des Zeichenfolgentyps in einer switch-Anweisung zulässt , kann der Compiler vermutlich keinen Code für eine Implementierung mit konstanter Zeitverzweigung für diesen Typ generieren und muss einen Wenn-Dann-Stil generieren.

@mweerden - Ah ich verstehe. Vielen Dank.

Ich habe nicht viel Erfahrung mit C # und .NET, aber es scheint, dass die Sprachdesigner keinen statischen Zugriff auf das Typsystem zulassen, außer unter engen Umständen. Das Schlüsselwort typeof gibt ein Objekt zurück, sodass nur zur Laufzeit darauf zugegriffen werden kann .

Henk
quelle
0

Ich denke, Henk hat es mit der Sache "Kein statischer Zugang zum Typensystem" geschafft

Eine andere Option ist, dass es keine Reihenfolge für Typen gibt, bei denen Zahlen und Zeichenfolgen verwendet werden können. Ein Typschalter kann also keinen binären Suchbaum erstellen, sondern nur eine lineare Suche.

BCS
quelle
0

Ich stimme diesem Kommentar zu, dass die Verwendung eines tabellengesteuerten Ansatzes oft besser ist.

In C # 1.0 war dies nicht möglich, da es keine Generika und anonymen Delegaten gab. Neue Versionen von C # verfügen über das Gerüst, damit dies funktioniert. Es hilft auch, eine Notation für Objektliterale zu haben.

HS.
quelle
0

Ich habe praktisch keine Kenntnisse über C #, aber ich vermute, dass entweder der Wechsel einfach so vorgenommen wurde, wie er in anderen Sprachen vorkommt, ohne darüber nachzudenken, ihn allgemeiner zu gestalten, oder der Entwickler entschied, dass sich eine Erweiterung nicht lohnt.

Genau genommen haben Sie absolut Recht, dass es keinen Grund gibt, diese Einschränkungen aufzuerlegen. Man könnte vermuten, dass der Grund dafür ist, dass die Implementierung für die zulässigen Fälle sehr effizient ist (wie von Brian Ensink ( 44921 ) vorgeschlagen), aber ich bezweifle, dass die Implementierung sehr effizient ist (wrt if-Anweisungen), wenn ich Ganzzahlen und einige zufällige Fälle verwende (zB 345, -4574 und 1234203). Und auf jeden Fall, was schadet es, wenn man alles (oder zumindest mehr) zulässt und sagt, dass es nur für bestimmte Fälle (wie (fast) aufeinanderfolgende Zahlen) effizient ist?

Ich kann mir jedoch vorstellen, dass man Typen aus Gründen wie dem von lomaxx ( 44918) ausschließen möchte ) .

Bearbeiten: @Henk ( 44970 ): Wenn Zeichenfolgen maximal gemeinsam genutzt werden, sind Zeichenfolgen mit gleichem Inhalt auch Zeiger auf denselben Speicherort. Wenn Sie dann sicherstellen können, dass die in den Fällen verwendeten Zeichenfolgen nacheinander im Speicher gespeichert werden, können Sie den Switch sehr effizient implementieren (dh mit Ausführung in der Reihenfolge von 2 Vergleichen, einer Addition und zwei Sprüngen).

mweerden
quelle
0

Mit C # 8 können Sie dieses Problem elegant und kompakt mithilfe eines Schalterausdrucks lösen:

public string GetTypeName(object obj)
{
    return obj switch
    {
        int i => "Int32",
        string s => "String",
        { } => "Unknown",
        _ => throw new ArgumentNullException(nameof(obj))
    };
}

Als Ergebnis erhalten Sie:

Console.WriteLine(GetTypeName(obj: 1));           // Int32
Console.WriteLine(GetTypeName(obj: "string"));    // String
Console.WriteLine(GetTypeName(obj: 1.2));         // Unknown
Console.WriteLine(GetTypeName(obj: null));        // System.ArgumentNullException

Weitere Informationen zur neuen Funktion finden Sie hier .

smolchanovsky
quelle