Skip to content

Instantly share code, notes, and snippets.

@teoadal
Last active November 25, 2025 05:50
Show Gist options
  • Select an option

  • Save teoadal/95ae4122b547767a7e0f96214912df16 to your computer and use it in GitHub Desktop.

Select an option

Save teoadal/95ae4122b547767a7e0f96214912df16 to your computer and use it in GitHub Desktop.
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Order;
using FastEnumUtility;
namespace Benchmarks;
[SimpleJob(RuntimeMoniker.Net10_0)]
[SimpleJob(RuntimeMoniker.Net90)]
[SimpleJob(RuntimeMoniker.Net80)]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByJob)]
[MeanColumn, MemoryDiagnoser]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class EnumBenchmark
{
private Fruits[] _enumValues = null!;
[Benchmark(Baseline = true)]
public int PureDotnet()
{
var sum = 0;
foreach (var enumValue in _enumValues)
{
var x1 = Enum.GetName(enumValue);
var x2 = Enum.IsDefined(enumValue);
var stringValue = FruitsExtensions.ToString(enumValue);
var x3 = Enum.Parse<Fruits>(stringValue);
sum += Enum.TryParse<Fruits>(stringValue, out var value)
? HashCode.Combine(x1, x2, x3, value)
: 0;
}
return sum;
}
[Benchmark]
public int PureDotnet_ToString()
{
var sum = 0;
foreach (var enumValue in _enumValues)
{
var x1 = enumValue.ToString();
var x2 = Enum.IsDefined(enumValue);
var stringValue = FruitsExtensions.ToString(enumValue);
var x3 = Enum.Parse<Fruits>(stringValue);
sum += Enum.TryParse<Fruits>(stringValue, out var value)
? HashCode.Combine(x1, x2, x3, value)
: 0;
}
return sum;
}
[Benchmark]
public int SelfCode()
{
var sum = 0;
foreach (var enumValue in _enumValues)
{
var x1 = SelfEnumUtils<Fruits>.ToString(enumValue);
var x2 = SelfEnumUtils<Fruits>.IsDefined(enumValue);
var stringValue = FruitsExtensions.ToString(enumValue);
var x3 = SelfEnumUtils<Fruits>.Parse(stringValue);
sum += SelfEnumUtils<Fruits>.TryParse(stringValue, out var value)
? HashCode.Combine(x1, x2, x3, value)
: 0;
}
return sum;
}
[Benchmark]
public int WithFastEnum()
{
var sum = 0;
foreach (var enumValue in _enumValues)
{
var x1 = FastEnum.ToString<Fruits, FruitsBooster>(enumValue);
var x2 = FastEnum.IsDefined<Fruits, FruitsBooster>(enumValue);
var stringValue = FruitsExtensions.ToString(enumValue);
var x3 = FastEnum.Parse<Fruits, FruitsBooster>(stringValue);
sum += FastEnum.TryParse<Fruits, FruitsBooster>(stringValue, out var value)
? HashCode.Combine(x1, x2, x3, value)
: 0;
}
return sum;
}
[GlobalSetup]
public void Setup()
{
_enumValues = Enum.GetValues<Fruits>();
// warmup
Enum.GetValues<Fruits>();
FastEnum.GetValues<Fruits>();
SelfEnumUtils<Fruits>.GetValues();
}
}
[FastEnum<Fruits>]
public partial class FruitsBooster; // code generator will be generated here
public static class FruitsExtensions
{
public static string ToString(this Fruits value) => value switch
{
Fruits.Banana => nameof(Fruits.Banana),
Fruits.Peach => nameof(Fruits.Peach),
Fruits.Orange => nameof(Fruits.Orange),
Fruits.Grape => nameof(Fruits.Grape),
Fruits.Lemon => nameof(Fruits.Lemon),
Fruits.Melon => nameof(Fruits.Melon),
Fruits.Strawberry => nameof(Fruits.Strawberry),
Fruits.Cherry => nameof(Fruits.Cherry),
Fruits.WaterMelon => nameof(Fruits.WaterMelon),
Fruits.Pear => nameof(Fruits.Pear),
Fruits.Pineapple => nameof(Fruits.Pineapple),
_ => value.ToString()
};
}
public static class SelfEnumUtils<T>
where T : struct, Enum
{
private static readonly FrozenDictionary<string, T> StringToValues;
private static readonly FrozenDictionary<T, string> ValuesToString;
static SelfEnumUtils()
{
var enums = Enum.GetValues<T>();
var values = new Dictionary<T, string>(enums.Length);
foreach (var value in enums)
{
values.TryAdd(value, value.ToString());
}
StringToValues = values.ToFrozenDictionary(static value => value.Value, static value => value.Key);
ValuesToString = values.ToFrozenDictionary();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ImmutableArray<T> GetValues() => ValuesToString.Keys;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDefined(T value) => ValuesToString.ContainsKey(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Parse(string stringValue) => StringToValues.TryGetValue(stringValue, out var value)
? value
: Throw<T>($"Value '{stringValue}' is not defined");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToString(T value) => ValuesToString.TryGetValue(value, out var stringValue)
? stringValue
: Throw<string>($"Value '{value}' is not defined");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParse(string stringValue, out T value) => StringToValues.TryGetValue(stringValue, out value);
[MethodImpl(MethodImplOptions.NoInlining)]
private static TResult Throw<TResult>(string message) => throw new Exception(message);
}
public enum Fruits : byte
{
Unknown = 0,
Banana,
Peach,
Orange,
Grape,
Lemon,
Melon,
Strawberry,
Cherry,
WaterMelon,
Pear,
Pineapple,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment