Gregs Profilergebnisse sind für das genaue Szenario, das er abdeckte, großartig, aber interessanterweise ändern sich die relativen Kosten der verschiedenen Methoden dramatisch, wenn eine Reihe verschiedener Faktoren berücksichtigt werden, einschließlich der Anzahl der verglichenen Typen sowie der relativen Häufigkeit und aller Muster in den zugrunde liegenden Daten .
Die einfache Antwort lautet: Niemand kann Ihnen sagen, wie hoch der Leistungsunterschied in Ihrem speziellen Szenario sein wird. Sie müssen die Leistung in Ihrem eigenen System auf unterschiedliche Weise selbst messen, um eine genaue Antwort zu erhalten.
Die If / Else-Kette ist ein effektiver Ansatz für eine kleine Anzahl von Typvergleichen oder wenn Sie zuverlässig vorhersagen können, welche wenigen Typen die Mehrheit der angezeigten Typen ausmachen werden. Das potenzielle Problem bei diesem Ansatz besteht darin, dass mit zunehmender Anzahl von Typen auch die Anzahl der Vergleiche zunimmt, die ausgeführt werden müssen.
wenn ich folgendes ausführe:
int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...
Jede der vorherigen if-Bedingungen muss ausgewertet werden, bevor der richtige Block eingegeben wird. Andererseits
switch(value) {
case 0:...break;
case 1:...break;
case 2:...break;
...
case 25124:...break;
}
führt einen einfachen Sprung zum richtigen Codebit durch.
In Ihrem Beispiel wird es komplizierter, wenn Ihre andere Methode einen Schalter für Zeichenfolgen anstelle von Ganzzahlen verwendet, was etwas komplizierter wird. Auf einer niedrigen Ebene können Zeichenfolgen nicht auf die gleiche Weise wie ganzzahlige Werte eingeschaltet werden, sodass der C # -Compiler etwas Magie ausübt, damit dies für Sie funktioniert.
Wenn die switch-Anweisung "klein genug" ist (wobei der Compiler automatisch das tut, was er für am besten hält), generiert das Einschalten von Zeichenfolgen Code, der mit einer if / else-Kette identisch ist.
switch(someString) {
case "Foo": DoFoo(); break;
case "Bar": DoBar(); break;
default: DoOther; break;
}
ist das gleiche wie:
if(someString == "Foo") {
DoFoo();
} else if(someString == "Bar") {
DoBar();
} else {
DoOther();
}
Sobald die Liste der Elemente im Wörterbuch "groß genug" wird, erstellt der Compiler automatisch ein internes Wörterbuch, das die Zeichenfolgen im Switch einem ganzzahligen Index und anschließend einem auf diesem Index basierenden Switch zuordnet.
Es sieht ungefähr so aus (Stellen Sie sich mehr Einträge vor, als ich tippen werde)
Ein statisches Feld wird an einem "versteckten" Ort definiert, der der Klasse zugeordnet ist, die die switch-Anweisung vom Typ enthält Dictionary<string, int>
und einen verstümmelten Namen erhält
if(theDictionary == null) {
theDictionary = new Dictionary<string,int>();
theDictionary["Foo"] = 0;
theDictionary["Bar"] = 1;
}
int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
switch(switchIndex) {
case 0: DoFoo(); break;
case 1: DoBar(); break;
}
} else {
DoOther();
}
In einigen Schnelltests, die ich gerade ausgeführt habe, ist die If / Else-Methode ungefähr dreimal so schnell wie der Wechsel für 3 verschiedene Typen (wobei die Typen zufällig verteilt sind). Bei 25 Typen ist der Switch um einen kleinen Abstand (16%) schneller. Bei 50 Typen ist der Switch mehr als doppelt so schnell.
Wenn Sie eine große Anzahl von Typen einschalten möchten, würde ich eine dritte Methode vorschlagen:
private delegate void NodeHandler(ChildNode node);
static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();
private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();
ret[typeof(Bob).TypeHandle] = HandleBob;
ret[typeof(Jill).TypeHandle] = HandleJill;
ret[typeof(Marko).TypeHandle] = HandleMarko;
return ret;
}
void HandleChildNode(ChildNode node)
{
NodeHandler handler;
if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
{
handler(node);
}
else
{
}
}
Dies ähnelt dem, was Ted Elliot vorgeschlagen hat, aber die Verwendung von Laufzeit-Typ-Handles anstelle von Objekten vom vollständigen Typ vermeidet den Aufwand für das Laden des Typ-Objekts durch Reflexion.
Hier sind einige kurze Zeitangaben auf meiner Maschine:
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 5 Typen
Methodenzeit% des Optimums
If / Else 179,67 100,00
TypeHandleDictionary 321.33 178.85
TypeDictionary 377.67 210.20
Schalter 492.67 274.21
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 10 Typen
Methodenzeit% des Optimums
If / Else 271.33 100.00
TypeHandleDictionary 312.00 114.99
TypeDictionary 374.33 137.96
Schalter 490.33 180.71
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 15 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 312.00 100.00
If / Else 369.00 118.27
TypeDictionary 371.67 119.12
Schalter 491.67 157.59
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 20 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 335.33 100.00
TypeDictionary 373.00 111.23
If / Else 462.67 137.97
Schalter 490.33 146.22
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 25 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 319.33 100.00
TypeDictionary 371.00 116.18
Schalter 483.00 151.25
If / Else 562.00 175.99
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 50 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 319.67 100.00
TypeDictionary 376.67 117.83
Schalter 453.33 141.81
If / Else 1.032,67 323,04
Zumindest auf meinem Computer übertrifft der Typ-Handle-Wörterbuch-Ansatz alle anderen für mehr als 15 verschiedene Typen, wenn die Verteilung der als Eingabe für die Methode verwendeten Typen zufällig ist.
Wenn andererseits die Eingabe vollständig aus dem Typ besteht, der zuerst in der if / else-Kette überprüft wird, ist diese Methode viel schneller:
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = UniformFirst) und 50 Typen
Methodenzeit% des Optimums
If / Else 39.00 100.00
TypeHandleDictionary 317.33 813.68
TypeDictionary 396.00 1.015,38
Schalter 403.00 1.033.33
Wenn umgekehrt die Eingabe immer das Letzte in der if / else-Kette ist, hat dies den gegenteiligen Effekt:
Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = UniformLast) und 50 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 317.67 100.00
Schalter 354.33 111.54
TypeDictionary 377.67 118.89
If / Else 1.907,67 600,52
Wenn Sie einige Annahmen über Ihre Eingabe treffen können, erzielen Sie möglicherweise die beste Leistung mit einem hybriden Ansatz, bei dem Sie die wenigen am häufigsten verwendeten Typen prüfen und dann auf einen wörterbuchgesteuerten Ansatz zurückgreifen, wenn diese fehlschlagen.