Table of Contents

Class RequiredEnum<TSelf>

Namespace
Trellis
Assembly
Trellis.Core.dll

Base class for creating strongly-typed, behavior-rich enumeration value objects. Enum value objects are a DDD pattern that replaces C# enums with full-featured classes.

public abstract class RequiredEnum<TSelf> : IEquatable<RequiredEnum<TSelf>>, IComparable<RequiredEnum<TSelf>>, IComparable where TSelf : RequiredEnum<TSelf>, IScalarValue<TSelf, string>

Type Parameters

TSelf

The derived enum value object type itself (CRTP pattern).

Inheritance
RequiredEnum<TSelf>
Implements
Inherited Members
Extension Methods

Examples

Basic enum value object:

public partial class OrderState : RequiredEnum<OrderState>
{
    public static readonly OrderState Draft = new();
    public static readonly OrderState Confirmed = new();
    public static readonly OrderState Shipped = new();
    public static readonly OrderState Delivered = new();
    public static readonly OrderState Cancelled = new();
}

// The source generator automatically creates:
// - IScalarValue<OrderState, string> interface implementation
// - public static Result<OrderState> TryCreate(string value)
// - public static Result<OrderState> TryCreate(string? value, string? fieldName = null)
// - public static OrderState Parse(string s, IFormatProvider? provider)
// - public static bool TryParse(string? s, IFormatProvider? provider, out OrderState result)
// - [JsonConverter(typeof(RequiredEnumJsonConverter<OrderState>))] attribute

// Usage - Value defaults to the field name
var state = OrderState.Draft;           // Value = "Draft"
var all = OrderState.GetAll();
var result = OrderState.TryCreate("Draft");  // Result<OrderState>

Enum value object with behavior, using field names by default and an override only where needed:

public partial class PaymentMethod : RequiredEnum<PaymentMethod>
{
    public static readonly PaymentMethod CreditCard = new(fee: 0.029m);
    public static readonly PaymentMethod BankTransfer = new(fee: 0.005m);
    [EnumValue("cash-payment")]
    public static readonly PaymentMethod Cash = new(fee: 0m);

    public decimal Fee { get; }

    private PaymentMethod(decimal fee) => Fee = fee;

    public decimal CalculateFee(decimal amount) => amount * Fee;
}

// CreditCard.Value == "CreditCard"
// Cash.Value == "cash-payment"

Using in ASP.NET Core DTOs with automatic validation:

public record UpdateOrderDto
{
    public OrderState State { get; init; } = null!;
}

// In controller - validation happens automatically
[HttpPut("{id}")]
public IActionResult UpdateOrder(Guid id, UpdateOrderDto dto)
{
    // If we reach here, dto.State is already validated!
    return Ok(_orderService.UpdateState(id, dto.State));
}

Remarks

Enum value objects address limitations of C# enums:

  • Behavior: Each value can have associated behavior and properties
  • Type safety: Invalid values are impossible (no (OrderStatus)999)
  • Extensibility: Add methods, computed properties, and domain logic
  • State machines: Model valid transitions between states

Each enum value object member is defined as a static readonly field:

  • Members are discovered via reflection and cached for performance
  • The Value property is the semantic string value. It defaults to the field name and can be overridden with EnumValueAttribute only when the external name must differ.
  • The Ordinal property is secondary declaration-order metadata, not semantic identity

When used with the partial keyword, the PrimitiveValueObjectGenerator source generator automatically creates:

  • IScalarValue<TSelf, string> implementation for ASP.NET Core automatic validation
  • TryCreate(string) - Factory method for non-nullable strings (required by IScalarValue)
  • TryCreate(string?, string?) - Factory method with validation and custom field name
  • IParsable<T> implementation (Parse, TryParse)
  • JSON serialization support via RequiredEnumJsonConverter<T>
  • ASP.NET Core model binding from route/query/form/headers
  • OpenTelemetry activity tracing

Common use cases:

  • Order/payment/shipping statuses
  • User roles and permissions
  • Document states in workflows
  • Any finite set of domain values with behavior

Constructors

RequiredEnum()

Initializes a new instance. The symbolic value is assigned during member discovery.

protected RequiredEnum()

Properties

Ordinal

Gets auto-generated declaration-order metadata for the member. This is a secondary infrastructure/detail value, not semantic identity.

public int Ordinal { get; }

Property Value

int

Remarks

Ordinal values are assigned from declaration order (0, 1, 2, ...). Reordering fields changes ordinals, so they should not be treated as stable wire or storage contracts.

Ordinal is lazily initialized on first access to avoid chicken-and-egg issues with static field initialization order.

Value

Gets the string value of this enum value object member. Defaults to the field name during discovery and can be overridden with EnumValueAttribute.

public string Value { get; }

Property Value

string

Remarks

Value is lazily initialized on first access to avoid chicken-and-egg issues with static field initialization order.

Methods

CompareTo(RequiredEnum<TSelf>?)

Compares this instance with another by Ordinal (declaration order), so members sort in the order they are declared — matching how the C# enum this type replaces would sort by its underlying value. Equality remains keyed on Value; the two stay consistent because each member has a unique Value and a unique Ordinal.

public int CompareTo(RequiredEnum<TSelf>? other)

Parameters

other RequiredEnum<TSelf>

The instance to compare with, or null.

Returns

int

A negative value if this instance precedes other; zero if they are equal; a positive value if this instance follows other or other is null.

Equals(object?)

Determines whether the specified object is equal to the current object.

public override bool Equals(object? obj)

Parameters

obj object

The object to compare with the current object.

Returns

bool

true if the specified object is equal to the current object; otherwise, false.

Equals(RequiredEnum<TSelf>?)

Indicates whether the current object is equal to another object of the same type.

public bool Equals(RequiredEnum<TSelf>? other)

Parameters

other RequiredEnum<TSelf>

An object to compare with this object.

Returns

bool

true if the current object is equal to the other parameter; otherwise, false.

GetAll()

Gets all defined members of this enum value object type.

public static IReadOnlyCollection<TSelf> GetAll()

Returns

IReadOnlyCollection<TSelf>

GetHashCode()

Serves as the default hash function.

public override int GetHashCode()

Returns

int

A hash code for the current object.

Is(params TSelf[])

Checks if this instance is one of the specified values.

public bool Is(params TSelf[] values)

Parameters

values TSelf[]

The values to compare against.

Returns

bool

true if this instance matches any of the specified values; otherwise, false.

IsNot(params TSelf[])

Checks if this instance is not one of the specified values.

public bool IsNot(params TSelf[] values)

Parameters

values TSelf[]

The values to compare against.

Returns

bool

true if this instance does not match any of the specified values; otherwise, false.

ToString()

Returns a string that represents the current object.

public override string ToString()

Returns

string

A string that represents the current object.

TryCreate(string)

Creates a validated member from its symbolic value (case-insensitive). This is the public factory every Required* primitive exposes; the JSON converter, EF converter, and Parse/TryParse all route through it. Provided by the base because an enum's creation is a uniform name lookup, so — unlike the scalar primitives — it needs no per-derived-type generation.

public static Result<TSelf> TryCreate(string value)

Parameters

value string

The symbolic value to look up.

Returns

Result<TSelf>

A Result<TValue> containing the matching member or a validation error.

TryCreate(string?, string?)

Creates a validated member from its symbolic value (case-insensitive), reporting validation failures against fieldName.

public static Result<TSelf> TryCreate(string? value, string? fieldName = null)

Parameters

value string

The symbolic value to look up.

fieldName string

Optional field name for validation error messages.

Returns

Result<TSelf>

A Result<TValue> containing the matching member or a validation error.

Operators

operator ==(RequiredEnum<TSelf>?, RequiredEnum<TSelf>?)

Determines whether two instances are equal.

public static bool operator ==(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right)

Parameters

left RequiredEnum<TSelf>
right RequiredEnum<TSelf>

Returns

bool

operator >(RequiredEnum<TSelf>?, RequiredEnum<TSelf>?)

Determines whether the left instance follows the right in declaration order (Ordinal).

public static bool operator >(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right)

Parameters

left RequiredEnum<TSelf>
right RequiredEnum<TSelf>

Returns

bool

operator >=(RequiredEnum<TSelf>?, RequiredEnum<TSelf>?)

Determines whether the left instance follows or equals the right in declaration order (Ordinal).

public static bool operator >=(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right)

Parameters

left RequiredEnum<TSelf>
right RequiredEnum<TSelf>

Returns

bool

implicit operator string(RequiredEnum<TSelf>)

Implicitly converts the enum value object to its symbolic Value. This mirrors the implicit unwrap that ScalarValueObject<TSelf, T>-derived primitives provide, so every Required* value object can be used transparently as its underlying primitive. Member-to-member equality is unaffected: the == operator above is an exact match and wins over a double conversion to string, so it keeps its case-insensitive Value semantics.

public static implicit operator string(RequiredEnum<TSelf> value)

Parameters

value RequiredEnum<TSelf>

The enum value object to convert.

Returns

string

The member's symbolic Value.

operator !=(RequiredEnum<TSelf>?, RequiredEnum<TSelf>?)

Determines whether two instances are not equal.

public static bool operator !=(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right)

Parameters

left RequiredEnum<TSelf>
right RequiredEnum<TSelf>

Returns

bool

operator <(RequiredEnum<TSelf>?, RequiredEnum<TSelf>?)

Determines whether the left instance precedes the right in declaration order (Ordinal).

public static bool operator <(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right)

Parameters

left RequiredEnum<TSelf>
right RequiredEnum<TSelf>

Returns

bool

operator <=(RequiredEnum<TSelf>?, RequiredEnum<TSelf>?)

Determines whether the left instance precedes or equals the right in declaration order (Ordinal).

public static bool operator <=(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right)

Parameters

left RequiredEnum<TSelf>
right RequiredEnum<TSelf>

Returns

bool