Intellenum

repository·main·Indexed 42 days ago

https://github.com/stevedunn/intellenum

A high-performance C# library that replaces standard enums with source-generated partial classes. It provides allocation-free, fast lookups for enum members by name or value using methods like FromName and FromValue. Intellenum supports a wide range of underlying types, including primitives, Guid, and DateTime, and integrates with System.Text.Json, Newtonsoft.Json, Dapper, and Entity Framework Core.

Tokens
20K
Snippets
67
Records
88
Agent score
53%

What's inside Intellenum

  1. Overview of Intellenum

    main

    Intellenum is an open-source C# library designed for fast and efficient enum handling. It uses C# source generation to create backing code that enables extremely fast, allocation-free lookups using methods like FromName, FromValue, and their Try... variants.

    Unlike standard C# enums, Intellenum is designed to be used with partial class definitions, allowing you to define static members that represent your enum values. It also includes code analyzers to prevent common safety issues, such as accidental defaulting.

    [Intellenum]
    public partial class CustomerType
    {
        public static readonly CustomerType Standard, Gold;
    }
  2. Performance Characteristics of Intellenum

    main

    Intellenum is designed for high performance, often outperforming standard C# enums and other smart enum libraries in key operations.

    Key performance highlights:

    • IsDefined / TryFromValue: Significantly faster than standard Enum.IsDefined or Enum.TryParse.
    • ToString(): Optimized for speed, comparable to or faster than SmartEnums.
    • FromName(): Highly efficient lookup compared to standard Enum.TryParse.
    • Value Access: Provides near-instant access to the underlying value.
  3. Choosing between records and classes/structs in Vogen

    main

    When using Vogen, it is recommended to use class or struct instead of record (or record class/record struct).

    Because Vogen is designed to wrap and protect a single primitive value, the standard benefits of C# records (like built-in value-based equality and boilerplate reduction) do not align with Vogen's purpose. Using records introduces specific side effects and behaviors that may not be desired in a Value Object context.

  4. Serialization and Deserialization support

    main

    Intellenum supports serialization and deserialization using the following libraries:

    • System.Text.Json
    • Newtonsoft.Json
    • Dapper
    • Entity Framework Core
    • Linq2Db
    • TypeConverters

    By default, Intellenum serializes using the Value property, matching the behavior of native C# enums. If you use System.Text.Json, you can also serialize dictionaries that use an Intellenum as a key.

  5. Behavior of ToString() and TypeConverters

    main

    ToString()

    Calling .ToString() on an Intellenum member returns the name of the member.

    TypeConverters

    Intellenum provides a TypeConverter that behaves differently depending on the target type:

    • Converting a member to a string returns the value of the member as a string.
    • It can also convert an underlying type back to its matching Intellenum enum.
  6. How IComparable is hoisted from underlying primitives

    main

    If the underlying primitive implements IComparable<>, Intellenum can hoist this functionality to the generated wrapper.

    To enable this, you must set the ComparisonGeneration configuration to UseUnderlying. When enabled, the generated wrapper will implement both IComparable<> (with the generic argument being the type of the wrapper) and the non-generic IComparable. The resulting method signature is public [ReturnType] CompareTo([primitive] other).

  7. How Intellenum implements IsDefined and TryFromValue

    main

    Intellenum uses different strategies for code generation based on the underlying type to maximize speed:

    Constant Types

    For types that can be used in a C# switch expression (compile-time constants), Intellenum generates a switch expression or statement. Supported types include:

    • byte / unsigned byte
    • int16 / unsigned int16
    • int32 / unsigned int32
    • int64
    • string
    • decimal

    Example of generated IsDefined for constant types:

    public static bool IsDefined(System.Int32 value)
    {
        return value switch { 
          1 => true,
          2 => true,
          3 => true,
          4 => true,
          _ => false
        };
    }

    Example of generated TryFromValue for constant types:

    public static bool TryFromValue(System.Int32 value, out CustomerType member)
    {
      switch (value) 
      {
          case 1:
              member = CustomerType.Unspecified; 
              return true;
          case 2:
              member = CustomerType.Normal; 
              return true;
          case 3:
              member = CustomerType.Gold; 
              return true;
          case 4:
              member = CustomerType.Diamond; 
              return true;
          default:
              member = default;
              return false;
      }
    }

    Non-Constant Types

    For types that cannot be used on the left-hand side of a switch expression (e.g., Guid, DateTime), Intellenum uses a lookup table (dictionary) to map between names and values.

  8. Serialization and Type Conversion in Intellenum

    main

    Intellenum supports various serialization and conversion workflows:

    • Serialization: It serializes using the Value property, similar to native C# enums. It is compatible with:
      • System.Text.Json
      • Newtonsoft.Json
      • Dapper
      • Entity Framework Core
      • Linq2Db
      • TypeConverters
    • Dictionary Keys: You can use an Intellenum as a key in a Dictionary, and it can be serialized (specifically supported by System.Text.Json).
    • ToString() Behavior: Calling .ToString() on a member returns the name of the member. However, the provided TypeConverter will return the value of the member as a string when converting to a string type.
    • TypeConverters: They can convert an underlying type back into its matching Intellenum.
  9. How TryParse behaves in Intellenum

    main

    When calling TryParse on a wrapper, the following lifecycle occurs:

    1. The method calls the underlying primitive's TryParse method.
    2. If parsing succeeds, the resulting value is passed to the wrapper's Validate method.
    3. If Validate fails, TryParse returns false. The out parameter will contain:
      • null for classes.
      • The default value for structs (note: accessing this default value may throw if it is a value object).

    Important Change (v4.0+): In version 4.0 and later, TryParse no longer throws a ValueObjectValidationException on validation failure; it follows the idiomatic pattern of returning false instead.

  10. How Hoisting works with IComparable and TryParse

    main

    Intellenum implements 'Hoisting' to inherit capabilities from the underlying type:

    1. IComparable: If the underlying type implements IComparable, the generated Intellenum will also implement IComparable, allowing you to use comparison operators (>, <) directly on the enum instances.
    2. TryParse: If the underlying type contains a static TryParse method, the generated Intellenum will also have a TryParse method. This generated method first calls the underlying type's TryParse and then performs a lookup using TryFromValue to find the matching enum member.
    // Example of comparison hoisting
    [Intellenum<Planet>]
    public partial class PlanetEnum { ... }
    
    // If Planet implements IComparable, this works:
    Console.WriteLine(PlanetEnum.Mars < PlanetEnum.Jupiter); 
    
    // Example of TryParse hoisting
    // If Planet has a static TryParse(string, out Planet)
    bool r = PlanetEnum.TryParse("Brown-273400", out var p);
  11. How parsing functionality is hoisted from underlying primitives

    main
    Intellenum automatically 'hoists' parsing capabilities from the underlying primitive type to the generated wrapper. This means any method named Parse or TryParse present on the primitive is available on the wrapper. Additionally, if the primitive implements the IParsable family of interfaces (including ISpanParsable and IUtf8SpanParsable) publicly, these interfaces are also hoisted to the wrapper.
  12. How Type Converters enable automatic serialization

    main

    Intellenum automatically generates a TypeConverter for every Value Object defined with the [ValueObject<T>] attribute.

    When an ASP.NET Core application receives a request, the routing framework inspects the parameter types. If a parameter is a Value Object, the framework looks for the associated TypeConverter (e.g., CityNameTypeConverter). It then calls the converter's ConvertTo method, which internally calls the Value Object's From method to transform the primitive input (such as a string) into the Value Object instance.

    // Example of the generated attribute applied to your struct
    [TypeConverter(typeof(CityNameTypeConverter))]
    public partial struct CityName
    {
    }