enum case handling - better to use a switch or a dictionary?

前端 未结 4 2042
逝去的感伤
逝去的感伤 2021-02-20 16:20

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.

4条回答
  •  深忆病人
    2021-02-20 16:39

    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.

提交回复
热议问题