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

前端 未结 4 2040
逝去的感伤
逝去的感伤 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:51

    This question seems to be looking for the fastest method of retrieving an item which is mapped to an Enum constant.

    The underlying type for almost all Enum types, which are not bit fields (i.e. not declared as [Flags]), is a 32-bit signed integer. There are sound performance reasons for this. The only real reason to use something different is if you absolutely must minimise memory usage. Bit fields are a different matter but we're not concerned with them here.

    In this typical scenario, an array map is ideal (and usually faster than a switch). Here's some generic code which is concise and optimised for retrieval. Unfortunately, due to the limitations of .NET generic constraints, a few hacks are required (such as having to pass a casting delegate into the instance constructor).

    using System;
    using System.Runtime.CompilerServices;
    
    namespace DEMO
    {
        public sealed class EnumMapper where TKey : struct, IConvertible
        {
            private struct FlaggedValue
            {
                public bool flag;
                public T value;
            }
    
            private static readonly int size;
            private readonly Func func;
            private FlaggedValue[] flaggedValues;
    
            public TValue this[TKey key]
            {
                get
                {
                    int index = this.func.Invoke(key);
    
                    FlaggedValue flaggedValue = this.flaggedValues[index];
    
                    if (flaggedValue.flag == false)
                    {
                        EnumMapper.ThrowNoMappingException(); // Don't want the exception code in the method. Make this callsite as small as possible to promote JIT inlining and squeeze out every last bit of performance.
                    }
    
                    return flaggedValue.value;
                }
            }
    
            static EnumMapper()
            {
                Type keyType = typeof(TKey);
    
                if (keyType.IsEnum == false)
                {
                    throw new Exception("The key type [" + keyType.AssemblyQualifiedName + "] is not an enumeration.");
                }
    
                Type underlyingType = Enum.GetUnderlyingType(keyType);
    
                if (underlyingType != typeof(int))
                {
                    throw new Exception("The key type's underlying type [" + underlyingType.AssemblyQualifiedName + "] is not a 32-bit signed integer.");
                }
    
                var values = (int[])Enum.GetValues(keyType);
    
                int maxValue = 0;
    
                foreach (int value in values)
                {
                    if (value < 0)
                    {
                        throw new Exception("The key type has a constant with a negative value.");
                    }
    
                    if (value > maxValue)
                    {
                        maxValue = value;
                    }
                }
    
                EnumMapper.size = maxValue + 1;
            }
    
            public EnumMapper(Func func)
            {
                if (func == null)
                {
                    throw new ArgumentNullException("func",
                                                    "The func cannot be a null reference.");
                }
    
                this.func = func;
    
                this.flaggedValues = new FlaggedValue[EnumMapper.size];
            }
    
            public static EnumMapper Construct(Func func)
            {
                return new EnumMapper(func);
            }
    
            public EnumMapper Map(TKey key,
                                                TValue value)
            {
                int index = this.func.Invoke(key);
    
                FlaggedValue flaggedValue;
    
                flaggedValue.flag = true;
                flaggedValue.value = value;
    
                this.flaggedValues[index] = flaggedValue;
    
                return this;
            }
    
            [MethodImpl(MethodImplOptions.NoInlining)]
            private static void ThrowNoMappingException()
            {
                throw new Exception("No mapping exists corresponding to the key.");
            }
        }
    }
    

    You can then simply initialise the mappings using a nice fluent interface:

    var mapper = EnumMapper.Construct((x) => (int)x)
                                                .Map(EnumType.Constant1, value1)
                                                .Map(EnumType.Constant2, value2)
                                                .Map(EnumType.Constant3, value3)
                                                .Map(EnumType.Constant4, value4)
                                                .Map(EnumType.Constant5, value5);
    

    And easily retrieve the mapped value:

    ValueType value = mapper[EnumType.Constant3];
    

    The x86 assembly (generated using the Visual Studio 2013 compiler) for the retrieval method is minimal:

    000007FE8E9909B0  push        rsi  
    000007FE8E9909B1  sub         rsp,20h  
    000007FE8E9909B5  mov         rsi,rcx  
    000007FE8E9909B8  mov         rax,qword ptr [rsi+8]  
    000007FE8E9909BC  mov         rcx,qword ptr [rax+8]  
    000007FE8E9909C0  call        qword ptr [rax+18h]  // The casting delegate's callsite is optimised to just two instructions 
    000007FE8E9909C3  mov         rdx,qword ptr [rsi+10h]  
    000007FE8E9909C7  mov         ecx,dword ptr [rdx+8]  
    000007FE8E9909CA  cmp         eax,ecx  
    000007FE8E9909CC  jae         000007FE8E9909ED  
    000007FE8E9909CE  movsxd      rax,eax  
    000007FE8E9909D1  lea         rax,[rdx+rax*8+10h]  
    000007FE8E9909D6  movzx       edx,byte ptr [rax]  
    000007FE8E9909D9  mov         esi,dword ptr [rax+4]  
    000007FE8E9909DC  test        dl,dl  
    000007FE8E9909DE  jne         000007FE8E9909E5  
    000007FE8E9909E0  call        000007FE8E9901B8  
    000007FE8E9909E5  mov         eax,esi  
    000007FE8E9909E7  add         rsp,20h  
    000007FE8E9909EB  pop         rsi  
    000007FE8E9909EC  ret  
    000007FE8E9909ED  call        000007FEEE411A08  
    000007FE8E9909F2  int         3 
    

提交回复
热议问题