UnitGenerator

repository·master·Indexed 19 days ago

https://github.com/cysharp/unitgenerator

A C# Source Generator that automates the creation of the Value Object pattern. It allows developers to wrap primitive types in type-safe structs using the [UnitOf] attribute to prevent accidental mixing of identifiers. It supports customizable behavior via UnitGenerateOptions, including arithmetic operators, comparison, validation, normalization, and native integration with System.Text.Json, MessagePack, Dapper, and Entity Framework Core.

Tokens
2.5K
Snippets
11
Records
13
Agent score
14%

What's inside UnitGenerator

  1. Configure Value Object behavior with UnitGenerateOptions

    master

    You can customize the generated code by passing UnitGenerateOptions flags to the [UnitOf] attribute. This allows you to enable specific features like arithmetic operators, comparison, or serialization support.

    Commonly used options include:

    • ArithmeticOperator: Enables arithmetic operations between the value object and itself.
    • ValueArithmeticOperator: Enables arithmetic operations between the value object and its underlying primitive type.
    • Comparable: Enables comparison operators (>, <, etc.).
    • MinMaxMethod: Generates Min and Max static methods.
    • JsonConverter / MessagePackFormatter / DapperTypeHandler / EntityFrameworkValueConverter: Enables native serialization/persistence for the specific framework.
    [UnitOf<int>(UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.Comparable)]
    public readonly partial struct Hp;
  2. Create a Value Object with UnitOfAttribute

    master

    UnitGenerator uses the [UnitOf] attribute on readonly partial struct declarations to generate a type-safe Value Object. This prevents accidental assignment between different identifiers (e.g., a UserId cannot be assigned to a ProductId) while allowing you to wrap primitive types like int, Guid, or string.

    For C# 11 and .NET 7 or later, you can use the generic syntax [UnitOf<T>].

    using UnitGenerator;
    
    // Using typeof syntax
    [UnitOf(typeof(int))]
    public readonly partial struct UserId; 
    
    // Using generic syntax (C# 11 / .NET 7+)
    [UnitOf<int>]
    public readonly partial struct UserId;
  3. Implement custom validation with Validate

    master

    When UnitGenerateOptions.Validate is enabled, the source generator adds a call to a partial void Validate() method inside the constructor. You can implement this method in your partial struct to enforce domain rules.

    [UnitOf(typeof(int), UnitGenerateOptions.Validate)]
    public readonly partial struct SampleValidate
    {
        private partial void Validate()
        {
            if (value > 9999) throw new Exception("Invalid value range: " + value);
        }
    }
  4. Integrate with Dapper, Entity Framework, and MessagePack

    master

    UnitGenerator provides built-in support for common serialization and data access frameworks:

    Dapper

    Enabling UnitGenerateOptions.DapperTypeHandler generates a nested TypeHandler class. This is automatically registered at the time of Module initialization.

    Entity Framework Core

    Enabling UnitGenerateOptions.EntityFrameworkValueConverter generates a nested ValueConverter class. Note: This is NOT registered automatically; you must register it manually in your DbContext configuration:

    builder.HasConversion(new UserId.UserIdValueConverter());

    MessagePack

    Enabling UnitGenerateOptions.MessagePackFormatter generates a nested IMessagePackFormatter<T> class, which is automatically used by MessagePackSerializer.

  5. Implement custom normalization with Normalize

    master

    When UnitGenerateOptions.Normalize is enabled, the source generator calls a partial void Normalize(ref T value) method during construction. This allows you to modify the underlying value during initialization (e.g., clamping values).

    [UnitOf(typeof(int), UnitGenerateOptions.Normalize)]
    public readonly partial struct SampleValidate
    {
        private partial void Normalize(ref int value)
        {
            value = Math.Max(value, 9999);
        }
    }
  6. Configure UnitGenerator using UnitGenerateOptions

    master

    UnitGenerator uses the UnitGenerateOptions bit flag enum to determine which methods and operators to implement for a generated unit struct. You pass these options to the [UnitOf] attribute.

    Available options include:

    • ImplicitOperator: Generates implicit conversions.
    • ParseMethod: Generates Parse and TryParse methods.
    • MinMaxMethod: Generates Min and Max methods.
    • ArithmeticOperator: Generates standard arithmetic operators.
    • ValueArithmeticOperator: Generates arithmetic operators between the unit type and its underlying value type.
    • Comparable: Implements IComparable<T> and comparison operators (>, <, etc.).
    • Validate: Generates a call to a partial void Validate() method in the constructor.
    • Normalize: Generates a call to a partial void Normalize(ref T value) method in the constructor.
    • JsonConverter: Implements System.Text.Json.JsonConverter.
    • MessagePackFormatter: Implements IMessagePackFormatter<T>.
    • DapperTypeHandler: Implements Dapper's SqlMapper.TypeHandler<T>.
    • EntityFrameworkValueConverter: Implements EF Core's ValueConverter<T, U>.
    [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.MinMaxMethod)]
    public readonly partial struct Strength { }
  7. Retrieve the underlying primitive value using AsPrimitive()

    master

    To avoid accidental conversion or logic errors, UnitGenerator does not generate a .Value property. Instead, use the AsPrimitive() method to retrieve the underlying value of the struct.

    // If UserId is [UnitOf<int>]
    int rawId = userId.AsPrimitive();
  8. Use Comparable with or without comparison operators

    master

    Enabling UnitGenerateOptions.Comparable implements IComparable<T> and the standard comparison operators (>, <, >=, <=).

    If you want to implement IComparable<T> but not the comparison operators (for example, when using Guid where comparison might not be desired), use the UnitGenerateOptions.WithoutComparisonOperator flag alongside Comparable.

    [UnitOf(typeof(Guid), UnitGenerateOptions.Comparable | UnitGenerateOptions.WithoutComparisonOperator)]
    public readonly partial struct FooId { }
  9. Customize Arithmetic Operators with ArithmeticOperators

    master

    By default, if UnitGenerateOptions.ArithmeticOperator is selected, UnitGenerator generates all members conforming to System.Numerics.INumber<T>.

    To suppress the full set and generate only specific operators, use the ArithmeticOperators property within the [UnitOf] attribute. This accepts a bit flag of UnitArithmeticOperators.

    ValueGenerates
    UnitArithmeticOperators.AdditionT operator +(T, T)
    UnitArithmeticOperators.SubtractionT operator -(T, T)
    UnitArithmeticOperators.MultiplyT operator *(T, T), T operator +(T), T operator -(T)
    UnitArithmeticOperators.DivisionT operator /(T, T), T operator +(T), T operator -(T)
    UnitArithmeticOperators.IncrementT operator ++(T)
    UnitArithmeticOperators.DecrementT operator --(T)
    [UnitOf(
        typeof(int), 
        UnitGenerateOptions.ArithmeticOperator,
        ArithmeticOperators = UnitArithmeticOperators.Addition | UnitArithmeticOperators.Subtraction)]
    public readonly partial struct Hp { }
  10. Specialized behavior for Guid and Ulid types

    master

    When the underlying type is Guid or Ulid, UnitGenerator automatically implements static New() and New***() methods (e.g., UserId.New()).

    In .NET 9.0 or later, these methods accept an optional uuidV7 parameter. When set to true, they use Guid.CreateVersion7() internally.

    // For a [UnitOf<Guid>] struct named GroupId
    GroupId id = GroupId.New();
    GroupId idV7 = GroupId.New(uuidV7: true); // .NET 9+ only