Converting an enum value to a string using ToString() is expensive. In most cases, the performance impact is negligible. However, when ToString is called thousands of times per second, saving a few milliseconds matters.
C#
enum Color
{
AliceBlue,
AntiqueWhite,
Aqua,
Aquamarine,
Azure,
...
}
C#
Color value = Color.Aqua;
_ = value.ToString(); // 👈 we'll improve this one!
Each of the following implementations lets you write the following code:
C#
Color value = Color.Aqua;
_ = value.ToStringCached(); // 🚀🚀🚀
#Method 1: Using a dictionary
The first approach uses a Dictionary to cache the string value. This works for any enum type:
C#
public static class EnumExtensions
{
// You can use a Dictionary for single-threaded application
private static readonly ConcurrentDictionary<Enum, string> s_cache = new ConcurrentDictionary<Enum, string>();
public static string ToStringCached(this Enum value)
{
return s_cache.GetOrAdd(value, v => v.ToString());
}
}
#Method 2: Using a specialized dictionary
Performance improves significantly when using a dictionary typed to the specific enum (see the benchmark below):
C#
public static class EnumExtensions
{
// You can use a Dictionary for single-threaded application
private static readonly ConcurrentDictionary<Color, string> _cache = new ConcurrentDictionary<Color, string>();
public static string ToStringCached(this Color value)
{
return _cache.GetOrAdd(value, v => v.ToString());
}
}
#Method 3: Using an array
If the enum values are sequential, you can replace the dictionary with an array for even better performance.
C#
// ⚠ Only works if the enum values are sequential
// Also, this implementation doesn't support flags enum or undefined values
public static class EnumExtensions
{
private static readonly string[] s_enumStringValues = GetEnumStrings();
private static string[] GetEnumStrings()
{
System.Collections.IList list = Enum.GetValues(typeof(Color));
var result = new string[list.Count];
for (int i = 0; i < list.Count; i++)
{
result[i] = list[i].ToString();
}
return result;
}
public static string ToStringCached(this Color myEnum)
{
return s_enumStringValues[(int)myEnum]; // If the first value is not 0, you need to adapt the logic: ((int)myEnum - MyEnum.FirstValue)
}
}
#Method 4: Using a switch
The final approach hard-codes the mapping using a switch expression:
C#
public static class EnumExtensions
{
public static string ToStringCached(this Color myEnum)
{
return myEnum switch
{
Color.AliceBlue => nameof(Color.AliceBlue),
Color.AntiqueWhite => nameof(Color.AntiqueWhite),
Color.Aqua => nameof(Color.Value0),
Color.Aquamarine => nameof(Color.Aquamarine),
Color.Azure => nameof(Color.Azure),
...
_ => myEnum.ToString(),
}
}
}
#Benchmark
The source of the benchmark is available here.
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i5-6600 CPU 3.30GHz (Skylake), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=5.0.200-preview.20601.7
[Host] : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
RyuJitX64 : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
Job=RyuJitX64 Jit=RyuJit Platform=X64
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|
| ToString | 31.8725 ns | 0.3661 ns | 0.3425 ns | 0.0076 | - | - | 24 B |
| Dictionary | 26.8290 ns | 0.2416 ns | 0.2018 ns | 0.0076 | - | - | 24 B |
| TypedDictionary | 6.4664 ns | 0.0582 ns | 0.0486 ns | - | - | - | - |
| ConcurrentDictionary | 24.5398 ns | 0.2065 ns | 0.1831 ns | 0.0076 | - | - | 24 B |
| TypedConcurrentDictionary | 9.8136 ns | 0.1064 ns | 0.0995 ns | - | - | - | - |
| Array | 0.0000 ns | 0.0000 ns | 0.0000 ns | - | - | - | - |
| Switch | 1.6659 ns | 0.0845 ns | 0.0791 ns | - | - | - | - |
The results are not affected by the number of values in the enum. The benchmark tests enums with 4, 10, 50, and 100 values, and the results are consistent.
#Roslyn Source Generator
Writing these caching methods by hand is tedious. A better approach is to use a Roslyn Source Generator to generate the method automatically. You can use the package Meziantou.Framework.FastEnumToStringGenerator (NuGet package):
C#
[assembly: FastEnumToString(typeof(Sample.Color))]
namespace Sample
{
public enum Color
{
Blue,
Red,
Green,
}
class Program
{
static void Main()
{
Color color = Color.Green;
System.Console.WriteLine(color.ToStringFast());
}
}
}
Do you have a question or a suggestion about this post? Contact me!