Smart Enums Performance - PawelGerr/Thinktecture.Runtime.Extensions GitHub Wiki

FrozenDictionary in .NET 8+

When running on .NET 8 or higher, Smart Enums automatically use FrozenDictionary for internal lookups, providing better performance compared to regular dictionaries.

ReadOnlySpan Support in .NET 9+

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)
    {
        ...
    }
}

Custom Equality Comparers and the Span-Based Lookup

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.

Zero-Allocation JSON Deserialization

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.

⚠️ **GitHub.com Fallback** ⚠️