Trellis Value Object Taxonomy
Packages: Trellis.Core (DDD primitives Aggregate<T>, Entity<T>, ValueObject, Specification<T>, IDomainEvent, plus VO base classes RequiredString<TSelf>, RequiredGuid<TSelf>, RequiredInt<TSelf>, RequiredDecimal<TSelf>, RequiredEnum<TSelf>); Trellis.Primitives (concrete VOs only — EmailAddress, Money, etc.) | Namespaces: Trellis, Trellis.Primitives | Purpose: canonical category map for Trellis value-like types: scalar, symbolic, structured, and optionality wrappers.
Patterns Index
Use this table to pick the right base class before reading the per-type signatures below.
| Goal |
Canonical base / type |
See |
| Wrap a single primitive (string, Guid, int, decimal, enum) into a typed value object |
RequiredString<TSelf>, RequiredGuid<TSelf>, RequiredInt<TSelf>, RequiredDecimal<TSelf>, RequiredEnum<TSelf> (one base per primitive) |
RequiredString<TSelf>, RequiredGuid<TSelf>, RequiredInt<TSelf>, RequiredDecimal<TSelf>, RequiredEnum<TSelf> |
| Define a custom-validated scalar with no source-generated infrastructure |
ScalarValueObject<TSelf, T> + IScalarValue<TSelf, T> |
ScalarValueObject<TSelf, T> |
| Compose multiple value-typed fields into a structural value object |
ValueObject (override GetEqualityComponents) |
ValueObject |
| Wrap an entity with identity (mutable through methods) |
Entity<TId> |
See trellis-api-core.md → Domain-Driven Design |
| Wrap a consistency boundary with domain events |
Aggregate<TId> |
See trellis-api-core.md → Domain-Driven Design |
| Express expected absence of a value |
Maybe<T> |
See trellis-api-core.md → Maybe |
| Move a query predicate out of a repository |
Specification<T> |
See trellis-api-core.md → Domain-Driven Design |
| Pick a built-in concrete value object instead of writing your own |
EmailAddress, Money, CountryCode, Url, etc. |
See trellis-api-primitives.md |
Types
ValueObject
public abstract class ValueObject : IComparable<ValueObject>, IComparable, IEquatable<ValueObject>
| Name |
Type |
Description |
| — |
— |
Base type for all structural equality objects. |
| Signature |
Returns |
Description |
public override bool Equals(object? obj) |
bool |
Structural equality. |
public bool Equals(ValueObject? other) |
bool |
Strongly typed structural equality. |
public override int GetHashCode() |
int |
Cached structural hash. |
public virtual int CompareTo(ValueObject? other) |
int |
Ordered comparison by equality components. |
public static bool operator ==(ValueObject? a, ValueObject? b) |
bool |
Equality operator. |
public static bool operator !=(ValueObject? a, ValueObject? b) |
bool |
Inequality operator. |
public static bool operator <(ValueObject? left, ValueObject? right) |
bool |
Ordering operator. |
public static bool operator <=(ValueObject? left, ValueObject? right) |
bool |
Ordering operator. |
public static bool operator >(ValueObject? left, ValueObject? right) |
bool |
Ordering operator. |
public static bool operator >=(ValueObject? left, ValueObject? right) |
bool |
Ordering operator. |
ScalarValueObject<TSelf, T>
public abstract class ScalarValueObject<TSelf, T> : ValueObject, IConvertible, IFormattable
where TSelf : ScalarValueObject<TSelf, T>, IScalarValue<TSelf, T>
where T : IComparable
| Name |
Type |
Description |
Value |
T |
Canonical scalar identity. |
| Signature |
Returns |
Description |
public override string ToString() |
string |
Primitive string form. |
public static implicit operator T(ScalarValueObject<TSelf, T> valueObject) |
T |
Implicit unwrap. |
public static TSelf Create(T value) |
TSelf |
Throwing scalar factory. |
public string ToString(string? format, IFormatProvider? formatProvider) |
string |
Formattable support. |
IScalarValue<TSelf, TPrimitive>
public interface IScalarValue<TSelf, TPrimitive>
where TSelf : IScalarValue<TSelf, TPrimitive>
where TPrimitive : IComparable
| Name |
Type |
Description |
Value |
TPrimitive |
Required canonical scalar property. |
| Signature |
Returns |
Description |
static abstract Result<TSelf> TryCreate(TPrimitive value, string? fieldName = null) |
Result<TSelf> |
Primitive creation path. |
static abstract Result<TSelf> TryCreate(string? value, string? fieldName = null) |
Result<TSelf> |
String creation path. |
static virtual TSelf Create(TPrimitive value) |
TSelf |
Throwing convenience factory. |
public interface IFormattableScalarValue<TSelf, TPrimitive> : IScalarValue<TSelf, TPrimitive>
where TSelf : IFormattableScalarValue<TSelf, TPrimitive>
where TPrimitive : IComparable
| Name |
Type |
Description |
Value |
TPrimitive |
Inherited canonical scalar value. |
| Signature |
Returns |
Description |
static abstract Result<TSelf> TryCreate(string? value, IFormatProvider? provider, string? fieldName = null) |
Result<TSelf> |
Culture-aware string creation path used by numeric/date scalar types. |
RequiredString<TSelf>
public abstract class RequiredString<TSelf> : ScalarValueObject<TSelf, string>
where TSelf : RequiredString<TSelf>, IScalarValue<TSelf, string>
| Name |
Type |
Description |
Value |
string |
Canonical scalar identity for required text. |
Length |
int |
String convenience member. |
| Signature |
Returns |
Description |
public bool StartsWith(string value) |
bool |
Query helper. |
public bool Contains(string value) |
bool |
Query helper. |
public bool EndsWith(string value) |
bool |
Query helper. |
RequiredGuid<TSelf>
public abstract class RequiredGuid<TSelf> : ScalarValueObject<TSelf, Guid>
where TSelf : RequiredGuid<TSelf>, IScalarValue<TSelf, Guid>
| Name |
Type |
Description |
Value |
Guid |
Canonical scalar identity for non-empty GUIDs. |
| Signature |
Returns |
Description |
public static TSelf Create(Guid value) |
TSelf |
Inherited throwing scalar factory. |
RequiredInt<TSelf>
public abstract class RequiredInt<TSelf> : ScalarValueObject<TSelf, int>
where TSelf : RequiredInt<TSelf>, IScalarValue<TSelf, int>
| Name |
Type |
Description |
Value |
int |
Canonical scalar identity for required integers. |
| Signature |
Returns |
Description |
public static TSelf Create(int value) |
TSelf |
Inherited throwing scalar factory. |
RequiredDecimal<TSelf>
public abstract class RequiredDecimal<TSelf> : ScalarValueObject<TSelf, decimal>
where TSelf : RequiredDecimal<TSelf>, IScalarValue<TSelf, decimal>
| Name |
Type |
Description |
Value |
decimal |
Canonical scalar identity for required decimals. |
| Signature |
Returns |
Description |
public static TSelf Create(decimal value) |
TSelf |
Inherited throwing scalar factory. |
RequiredLong<TSelf>
public abstract class RequiredLong<TSelf> : ScalarValueObject<TSelf, long>
where TSelf : RequiredLong<TSelf>, IScalarValue<TSelf, long>
| Name |
Type |
Description |
Value |
long |
Canonical scalar identity for required longs. |
| Signature |
Returns |
Description |
public static TSelf Create(long value) |
TSelf |
Inherited throwing scalar factory. |
RequiredBool<TSelf>
public abstract class RequiredBool<TSelf> : ScalarValueObject<TSelf, bool>
where TSelf : RequiredBool<TSelf>, IScalarValue<TSelf, bool>
| Name |
Type |
Description |
Value |
bool |
Canonical scalar identity for required booleans. |
| Signature |
Returns |
Description |
public static TSelf Create(bool value) |
TSelf |
Inherited throwing scalar factory. |
RequiredDateTime<TSelf>
public abstract class RequiredDateTime<TSelf> : ScalarValueObject<TSelf, DateTime>
where TSelf : RequiredDateTime<TSelf>, IScalarValue<TSelf, DateTime>
| Name |
Type |
Description |
Value |
DateTime |
Canonical scalar identity for non-default dates. |
| Signature |
Returns |
Description |
public override string ToString() |
string |
Invariant ISO 8601 round-trip string. |
public static TSelf Create(DateTime value) |
TSelf |
Inherited throwing scalar factory. |
RequiredEnum<TSelf>
public abstract class RequiredEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TSelf>
: IEquatable<RequiredEnum<TSelf>>
where TSelf : RequiredEnum<TSelf>, IScalarValue<TSelf, string>
| Name |
Type |
Description |
Value |
string |
Canonical symbolic identity. |
Ordinal |
int |
Non-semantic declaration-order metadata. |
| Signature |
Returns |
Description |
public static IReadOnlyCollection<TSelf> GetAll() |
IReadOnlyCollection<TSelf> |
Returns all declared symbolic members. |
public static Result<TSelf> TryFromName(string? name, string? fieldName = null) |
Result<TSelf> |
Canonical symbolic lookup. |
public bool Is(params TSelf[] values) |
bool |
Membership check. |
public bool IsNot(params TSelf[] values) |
bool |
Negated membership check. |
public override string ToString() |
string |
Returns Value. |
public override int GetHashCode() |
int |
Case-insensitive symbolic hash. |
public override bool Equals(object? obj) |
bool |
Case-insensitive symbolic equality. |
public bool Equals(RequiredEnum<TSelf>? other) |
bool |
Case-insensitive symbolic equality. |
public static bool operator ==(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right) |
bool |
Equality operator. |
public static bool operator !=(RequiredEnum<TSelf>? left, RequiredEnum<TSelf>? right) |
bool |
Inequality operator. |
MonetaryAmount
public class MonetaryAmount : ScalarValueObject<MonetaryAmount, decimal>, IScalarValue<MonetaryAmount, decimal>, IFormattableScalarValue<MonetaryAmount, decimal>, IParsable<MonetaryAmount>
| Name |
Type |
Description |
Value |
decimal |
Canonical scalar identity for single-currency systems. |
Zero |
MonetaryAmount |
Cached zero instance. |
| Signature |
Returns |
Description |
public static Result<MonetaryAmount> TryCreate(decimal value, string? fieldName = null) |
Result<MonetaryAmount> |
Non-negative scalar creation path. |
public static Result<MonetaryAmount> TryCreate(decimal? value, string? fieldName = null) |
Result<MonetaryAmount> |
Nullable scalar creation path. |
public static Result<MonetaryAmount> TryCreate(string? value, string? fieldName = null) |
Result<MonetaryAmount> |
Invariant string creation path. |
public static Result<MonetaryAmount> TryCreate(string? value, IFormatProvider? provider, string? fieldName = null) |
Result<MonetaryAmount> |
Culture-aware string creation path. |
public Result<MonetaryAmount> Add(MonetaryAmount other) |
Result<MonetaryAmount> |
Scalar arithmetic. |
public Result<MonetaryAmount> Subtract(MonetaryAmount other) |
Result<MonetaryAmount> |
Scalar arithmetic. |
public Result<MonetaryAmount> Multiply(int quantity) |
Result<MonetaryAmount> |
Scalar arithmetic. |
public Result<MonetaryAmount> Multiply(decimal multiplier) |
Result<MonetaryAmount> |
Scalar arithmetic. |
public static Result<MonetaryAmount> Sum(IEnumerable<MonetaryAmount> values) |
Result<MonetaryAmount> |
Scalar accumulation. |
Percentage
public class Percentage : ScalarValueObject<Percentage, decimal>, IScalarValue<Percentage, decimal>, IFormattableScalarValue<Percentage, decimal>, IParsable<Percentage>
| Name |
Type |
Description |
Value |
decimal |
Canonical scalar identity for percentages. |
Zero |
Percentage |
Cached 0%. |
Full |
Percentage |
Cached 100%. |
| Signature |
Returns |
Description |
public static Result<Percentage> TryCreate(decimal value, string? fieldName = null) |
Result<Percentage> |
Numeric creation path. |
public static Result<Percentage> TryCreate(decimal? value, string? fieldName = null) |
Result<Percentage> |
Nullable numeric creation path. |
public static Result<Percentage> TryCreate(string? value, string? fieldName = null) |
Result<Percentage> |
Invariant string creation path. |
public static Result<Percentage> TryCreate(string? value, IFormatProvider? provider, string? fieldName = null) |
Result<Percentage> |
Culture-aware string creation path. |
public static Result<Percentage> FromFraction(decimal fraction, string? fieldName = null) |
Result<Percentage> |
Derived scalar factory. |
public decimal AsFraction() |
decimal |
Converts to fraction. |
public decimal Of(decimal amount) |
decimal |
Applies percentage to an amount. |
Money
public class Money : ValueObject
| Name |
Type |
Description |
Amount |
decimal |
One structured component. |
Currency |
CurrencyCode |
Second structured component. |
| Signature |
Returns |
Description |
public static Result<Money> TryCreate(decimal amount, string currencyCode, string? fieldName = null) |
Result<Money> |
Structured factory. |
public static Money Create(decimal amount, string currencyCode) |
Money |
Throwing structured factory. |
public Result<Money> Add(Money other) |
Result<Money> |
Structured arithmetic with currency match enforcement. |
public Result<Money> Subtract(Money other) |
Result<Money> |
Structured arithmetic with currency match enforcement. |
public Result<Money> Multiply(decimal multiplier) |
Result<Money> |
Structured arithmetic. |
public Result<Money> Multiply(int quantity) |
Result<Money> |
Structured arithmetic. |
public Result<Money> Divide(decimal divisor) |
Result<Money> |
Structured arithmetic. |
public Result<Money> Divide(int divisor) |
Result<Money> |
Structured arithmetic. |
public Result<Money[]> Allocate(params int[] ratios) |
Result<Money[]> |
Structured split operation. |
public static Result<Money> Zero(string currencyCode = "USD") |
Result<Money> |
Structured zero factory. |
public static Result<Money> Sum(IEnumerable<Money> values) |
Result<Money> |
Structured accumulation. |
Base class hierarchy
- Scalar value objects
ValueObject -> ScalarValueObject<TSelf, T> -> concrete scalars
- Required scalar bases:
RequiredString<TSelf>
RequiredGuid<TSelf>
RequiredInt<TSelf>
RequiredDecimal<TSelf>
RequiredLong<TSelf>
RequiredBool<TSelf>
RequiredDateTime<TSelf>
- Built-in scalar concretes:
Age
CountryCode
CurrencyCode
EmailAddress
Hostname
IpAddress
LanguageCode
MonetaryAmount
Percentage
PhoneNumber
Slug
Url
- Symbolic value objects
RequiredEnum<TSelf> is separate from ScalarValueObject<TSelf, T> but still uses Value as its canonical public identity.
- Structured value objects
- Optionality wrappers
Maybe<T> belongs to Trellis.Core; it wraps presence/absence and is not a value object category peer to scalar/symbolic/structured types.
Source-generated members
The primitive generator adds category-specific members to partial types:
RequiredGuid<TSelf>: NewUniqueV4(), NewUniqueV7(), TryCreate(Guid value, string? fieldName = null), TryCreate(Guid? requiredGuidOrNothing, string? fieldName = null), TryCreate(string? stringOrNull, string? fieldName = null), Create(string stringValue), Parse, TryParse, explicit cast, and ValidateAdditional(Guid value, string fieldName, ref string? errorMessage).
RequiredString<TSelf>: TryCreate(string? value, string? fieldName = null), Create(string? value, string? fieldName = null), Parse, TryParse, explicit cast, and ValidateAdditional(string value, string fieldName, ref string? errorMessage).
RequiredInt<TSelf>, RequiredDecimal<TSelf>, RequiredLong<TSelf>, and RequiredDateTime<TSelf>: the invariant TryCreate(string? stringOrNull, string? fieldName = null) overload plus the culture-aware TryCreate(string? value, IFormatProvider? provider, string? fieldName = null) overload, Create(string stringValue), Parse, TryParse, explicit cast, and ValidateAdditional(...).
RequiredBool<TSelf>: primitive/string factories, Parse, TryParse, explicit cast, and ValidateAdditional(bool value, string fieldName, ref string? errorMessage).
RequiredEnum<TSelf>: TryCreate(string value), TryCreate(string? value, string? fieldName = null), Parse, TryParse, and Create(string value). Generated enum creation uses TryFromName only.
Built-in primitives table
| Type |
Category |
Canonical identity |
Wire/storage shape |
Notes |
Age |
Scalar |
Value : int |
JSON number |
Numeric scalar. |
CountryCode |
Scalar |
Value : string |
JSON string |
ISO alpha-2. |
CurrencyCode |
Scalar |
Value : string |
JSON string |
ISO alpha-3. |
EmailAddress |
Scalar |
Value : string |
JSON string |
Domain string scalar. |
Hostname |
Scalar |
Value : string |
JSON string |
Domain string scalar. |
IpAddress |
Scalar |
Value : string |
JSON string |
Domain string scalar. |
LanguageCode |
Scalar |
Value : string |
JSON string |
ISO alpha-2. |
MonetaryAmount |
Scalar |
Value : decimal |
JSON number |
Single-currency amount; no currency component. |
Percentage |
Scalar |
Value : decimal |
JSON number |
Supports fraction helpers and % parsing. |
PhoneNumber |
Scalar |
Value : string |
JSON string |
Normalized E.164. |
Slug |
Scalar |
Value : string |
JSON string |
URL-safe slug. |
Url |
Scalar |
Value : string |
JSON string |
Absolute HTTP/HTTPS URL. |
RequiredEnum<TSelf> derivatives |
Symbolic |
Value : string |
JSON string |
Finite symbolic set with behavior. |
Money |
Structured |
Amount + Currency |
JSON object |
Use for multi-currency scenarios. |
Money vs MonetaryAmount
- Use
MonetaryAmount when the bounded context has a single external currency policy and the semantic identity is only the numeric amount.
- Use
Money when currency is part of the semantic identity and must travel with the value.
MonetaryAmount is a scalar value object.
Money is a structured value object.
Code examples
using System.Globalization;
using Trellis;
using Trellis.Primitives;
namespace Demo;
public partial class OrderId : RequiredGuid<OrderId> { }
public partial class OrderStatus : RequiredEnum<OrderStatus>
{
public static readonly OrderStatus Draft = new();
[EnumValue("awaiting-payment")]
public static readonly OrderStatus AwaitingPayment = new();
}
public static class Example
{
public static void Run()
{
// Scalar
var orderId = OrderId.NewUniqueV4();
var amount = MonetaryAmount.TryCreate("12.34", CultureInfo.InvariantCulture).Value;
// Symbolic
var status = OrderStatus.TryFromName("awaiting-payment").Value;
var isOpen = status.Is(OrderStatus.Draft, OrderStatus.AwaitingPayment);
// Structured
var subtotal = Money.Create(12.34m, "USD");
var shipping = Money.Create(2.00m, "USD");
var total = subtotal.Add(shipping).Value;
_ = (orderId, amount, isOpen, total);
}
}
Cross-references