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.
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