Smart Enums Performance - PawelGerr/Thinktecture.Runtime.Extensions GitHub Wiki
- FrozenDictionary in .NET 8+
- ReadOnlySpan Support in .NET 9+
- Custom Equality Comparers and the Span-Based Lookup
- Zero-Allocation JSON Deserialization
When running on .NET 8 or higher, Smart Enums automatically use FrozenDictionary for internal lookups, providing better performance compared to regular dictionaries.
String-based Smart Enums in .NET 9+ support ReadOnlySpan<char> operations for improved performance:
[SmartEnum<string>]
public partial class ProductType
{
public static readonly ProductType Electronics = new("Electronics");
// In .NET 9+, you can use ReadOnlySpan<char> for lookups - no string allocation needed
public static bool TryGet(ReadOnlySpan<char> key, out ProductType? item)
{
...
}
}The span-based lookup uses the alternate lookup of FrozenDictionary. That alternate lookup exists only if the key equality comparer implements IAlternateEqualityComparer<ReadOnlySpan<char>, string>. StringComparer.OrdinalIgnoreCase, which this library uses as the default comparer for string keys, implements it, and so does every comparer that the predefined ComparerAccessors provide for string. For these comparers the span-based lookup allocates nothing.
If you configure [KeyMemberEqualityComparer<...>] with a hand-written IEqualityComparer<string> that does not implement IAlternateEqualityComparer<ReadOnlySpan<char>, string>, then the generated code falls back to the string-based lookup and converts the span to a string on every call. The Smart Enum stays fully usable, but Get(ReadOnlySpan<char>), TryGet(ReadOnlySpan<char>) and the span-based JSON deserialization path allocate one string per lookup.
To keep the allocation-free path, implement IAlternateEqualityComparer<ReadOnlySpan<char>, string> in your comparer. The interface lives in System.Collections.Generic and requires three members.
public sealed class OrdinalStringComparer
: IEqualityComparer<string>, IAlternateEqualityComparer<ReadOnlySpan<char>, string>
{
public bool Equals(string? x, string? y) => String.Equals(x, y, StringComparison.Ordinal);
public int GetHashCode(string obj) => StringComparer.Ordinal.GetHashCode(obj);
public string Create(ReadOnlySpan<char> alternate) => alternate.ToString();
public bool Equals(ReadOnlySpan<char> alternate, string other) => alternate.SequenceEqual(other);
public int GetHashCode(ReadOnlySpan<char> alternate) => String.GetHashCode(alternate, StringComparison.Ordinal);
}[KeyMemberEqualityComparer<...>] does not take the comparer type itself. Pass the comparer through an implementation of IEqualityComparerAccessor<string> that returns it, as described in Custom equality comparer.
ReadOnlySpan<char> support also enables zero-allocation JSON deserialization with System.Text.Json on .NET 9+. See Zero-Allocation JSON Deserialization for details on how this works and configuration options.