When handling the values of an enum on a case by case basis, is it better to use a switch statement or a dictionary?
I would think that the dictionary would be faster.
In fact, a dictionary is slower. Really. Just write simple benchmark (I've added example with converting dictionary to array):
void Main()
{
for (int itFac = 0; itFac < 7; itFac++ ) {
var iterations = 100;
iterations *= (int)Math.Pow(10, itFac);
Console.WriteLine("Iterations: {0}", iterations);
{
Random r = new Random();
int maxFruits = 5;
var timer = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
var res = Fruits.GetSpanishEquivalentWithArray((Fruits.FruitType)r.Next(maxFruits));
}
Console.WriteLine("Array time: {0}", timer.Elapsed);
}
{
Random r = new Random();
int maxFruits = 5;
var timer = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
var res = Fruits.GetSpanishEquivalent((Fruits.FruitType)r.Next(maxFruits));
}
Console.WriteLine("Switch time : {0}", timer.Elapsed);
}
{
Random r = new Random();
int maxFruits = 5;
var timer = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++) {
var res = Fruits.GetSpanishEquivalentWithDictionary((Fruits.FruitType)r.Next(maxFruits));
}
Console.WriteLine("Dictionary time: {0}", timer.Elapsed);
}
Console.WriteLine();
}
}
class Fruits {
public enum FruitType
{
Other,
Apple,
Banana,
Mango,
Orange
}
public enum SpanishFruitType
{
Otra,
Manzana, // Apple
Naranja, // Orange
Platano, // Banana
// let's say they don't have mangos, because I don't remember the word for it.
}
public static SpanishFruitType GetSpanishEquivalent(FruitType typeOfFruit)
{
switch(typeOfFruit)
{
case FruitType.Apple:
return SpanishFruitType.Manzana;
case FruitType.Banana:
return SpanishFruitType.Platano;
case FruitType.Orange:
return SpanishFruitType.Naranja;
case FruitType.Mango:
case FruitType.Other:
return SpanishFruitType.Otra;
default:
throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
}
}
public static SpanishFruitType GetSpanishEquivalent(string typeOfFruit)
{
switch(typeOfFruit)
{
case "apple":
return SpanishFruitType.Manzana;
case "banana":
return SpanishFruitType.Platano;
case "orange":
return SpanishFruitType.Naranja;
case "mango":
case "other":
return SpanishFruitType.Otra;
default:
throw new Exception("what kind of fruit is " + typeOfFruit + "?!");
}
}
public static Dictionary EnglishToSpanishFruit = new Dictionary()
{
{FruitType.Apple, SpanishFruitType.Manzana}
,{FruitType.Banana, SpanishFruitType.Platano}
,{FruitType.Mango, SpanishFruitType.Otra}
,{FruitType.Orange, SpanishFruitType.Naranja}
,{FruitType.Other, SpanishFruitType.Otra}
};
public static SpanishFruitType GetSpanishEquivalentWithDictionary(FruitType typeOfFruit)
{
return EnglishToSpanishFruit[typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
}
public static SpanishFruitType[] EnglishToSpanishFruitArray;
static Fruits() {
EnglishToSpanishFruitArray = new SpanishFruitType[EnglishToSpanishFruit.Select(p => (int)p.Key).Max() + 1];
foreach (var pair in EnglishToSpanishFruit)
EnglishToSpanishFruitArray[(int)pair.Key] = pair.Value;
}
public static SpanishFruitType GetSpanishEquivalentWithArray(FruitType typeOfFruit)
{
return EnglishToSpanishFruitArray[(int)typeOfFruit]; // throws exception if it's not in the dictionary, which is fine.
}
}
Results:
Iterations: 100
Array time : 00:00:00.0108628
Switch time : 00:00:00.0002204
Dictionary time: 00:00:00.0008475
Iterations: 1000
Array time : 00:00:00.0000410
Switch time : 00:00:00.0000472
Dictionary time: 00:00:00.0004556
Iterations: 10000
Array time : 00:00:00.0006095
Switch time : 00:00:00.0011230
Dictionary time: 00:00:00.0074769
Iterations: 100000
Array time : 00:00:00.0043019
Switch time : 00:00:00.0047117
Dictionary time: 00:00:00.0611122
Iterations: 1000000
Array time : 00:00:00.0468998
Switch time : 00:00:00.0520848
Dictionary time: 00:00:00.5861588
Iterations: 10000000
Array time : 00:00:00.4268453
Switch time : 00:00:00.5002004
Dictionary time: 00:00:07.5352484
Iterations: 100000000
Array time : 00:00:04.1720282
Switch time : 00:00:04.9347176
Dictionary time: 00:00:56.0107932
What happens. Let's look on the generated IL code:
Fruits.GetSpanishEquivalent:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: switch (IL_002B, IL_001F, IL_0023, IL_002B, IL_0027)
IL_001D: br.s IL_002F
IL_001F: ldc.i4.1
IL_0020: stloc.0
IL_0021: br.s IL_004A
IL_0023: ldc.i4.3
IL_0024: stloc.0
IL_0025: br.s IL_004A
IL_0027: ldc.i4.2
IL_0028: stloc.0
IL_0029: br.s IL_004A
IL_002B: ldc.i4.0
IL_002C: stloc.0
IL_002D: br.s IL_004A
IL_002F: ldstr "what kind of fruit is "
IL_0034: ldarg.0
IL_0035: box UserQuery+Fruits.FruitType
IL_003A: ldstr "?!"
IL_003F: call System.String.Concat
IL_0044: newobj System.Exception..ctor
IL_0049: throw
IL_004A: ldloc.0
IL_004B: ret
What happens? Switch happens. For sequenced number of values switch can be optimized and replaced by jump to pointer from array. Why real array works faster than switch - dunno, it just works faster.
Well, if you do not work with enums, but with strings there is no real difference between switch and dictionary on small number of variants. With more and more variants dictionary becomes faster.
What to choose? Choose what is easier to read for you and your team. When you see that your solution creates performance issues you should replace Dictionary (if you use it) to switch or array like me. When your function of translation is rarely called there is no need to optimize it.
Talking about your case - to get translation, all solutions are bad. Translations must be stored in resources. There must be only one FruitType, no other enums.