Table of Contents

Class Money

Namespace
Trellis.Primitives
Assembly
Trellis.Primitives.dll

Represents a structured monetary value object composed of an amount and currency code. Provides type-safe arithmetic operations that prevent mixing different currencies.

[JsonConverter(typeof(CompositeValueObjectJsonConverter<Money>))]
public class Money : ValueObject, IComparable<ValueObject>, IComparable, IEquatable<ValueObject>
Inheritance
Money
Implements
Inherited Members
Extension Methods

Remarks

Money is intentionally not a scalar value object because its semantic identity spans both Amount and Currency. Money uses decimal for precision and stores amounts in the currency's base unit. All arithmetic operations enforce currency matching and return Result for error handling. Amounts are automatically rounded to the appropriate number of decimal places for the currency.

Properties

Amount

Gets the monetary amount in the currency's base unit.

public decimal Amount { get; }

Property Value

decimal

Currency

Gets the ISO 4217 currency code.

public CurrencyCode Currency { get; }

Property Value

CurrencyCode

Methods

Add(Money)

Adds two Money amounts if they have the same currency.

public Result<Money> Add(Money other)

Parameters

other Money

Returns

Result<Money>

Exceptions

ArgumentNullException

Thrown when other is null.

Allocate(params int[])

Allocates money across multiple parts using ratios. Ensures no money is lost to rounding by allocating remainder to first portions.

public Result<Money[]> Allocate(params int[] ratios)

Parameters

ratios int[]

The ratios to split by (e.g., [1, 2, 1] for 25%, 50%, 25%).

Returns

Result<Money[]>

Array of Money amounts matching the ratios.

Exceptions

ArgumentNullException

Thrown when ratios is null.

Create(decimal, string)

Creates a Money instance with the specified amount and currency code. Throws an exception if the amount or currency is invalid.

public static Money Create(decimal amount, string currencyCode)

Parameters

amount decimal

The monetary amount.

currencyCode string

ISO 4217 currency code (e.g., "USD", "EUR").

Returns

Money

The Money instance.

Remarks

Use this method when you know the values are valid (e.g., in tests or with constants). For user input, use TryCreate(decimal, string, string?) instead.

Exceptions

InvalidOperationException

Thrown when amount is negative or currency is invalid.

Divide(decimal)

Divides the money amount by a scalar value.

public Result<Money> Divide(decimal divisor)

Parameters

divisor decimal

Returns

Result<Money>

Divide(int)

Divides the money amount by an integer to split evenly.

public Result<Money> Divide(int divisor)

Parameters

divisor int

Returns

Result<Money>

GetEqualityComponents(ref EqualityComponents)

Gets the equality components for value comparison.

protected override void GetEqualityComponents(ref EqualityComponents components)

Parameters

components EqualityComponents

IsGreaterThan(Money)

Checks if this money is greater than another money amount (same currency required).

public bool IsGreaterThan(Money other)

Parameters

other Money

Returns

bool

Remarks

Returns false for cross-currency comparisons; mismatched currencies are neither greater than nor less than each other under this contract. Callers that need to distinguish "smaller" from "different currency" must compare Currency explicitly.

Exceptions

ArgumentNullException

Thrown when other is null.

IsGreaterThanOrEqual(Money)

Checks if this money is greater than or equal to another amount (same currency required).

public bool IsGreaterThanOrEqual(Money other)

Parameters

other Money

Returns

bool

Remarks

Returns false for cross-currency comparisons. See IsGreaterThan(Money).

Exceptions

ArgumentNullException

Thrown when other is null.

IsLessThan(Money)

Checks if this money is less than another money amount (same currency required).

public bool IsLessThan(Money other)

Parameters

other Money

Returns

bool

Remarks

Returns false for cross-currency comparisons. See IsGreaterThan(Money).

Exceptions

ArgumentNullException

Thrown when other is null.

IsLessThanOrEqual(Money)

Checks if this money is less than or equal to another amount (same currency required).

public bool IsLessThanOrEqual(Money other)

Parameters

other Money

Returns

bool

Remarks

Returns false for cross-currency comparisons. See IsGreaterThan(Money).

Exceptions

ArgumentNullException

Thrown when other is null.

Multiply(decimal)

Multiplies the money amount by a scalar value.

public Result<Money> Multiply(decimal multiplier)

Parameters

multiplier decimal

Returns

Result<Money>

Multiply(int)

Multiplies the money amount by an integer quantity.

public Result<Money> Multiply(int quantity)

Parameters

quantity int

Returns

Result<Money>

Subtract(Money)

Subtracts another Money amount if they have the same currency.

public Result<Money> Subtract(Money other)

Parameters

other Money

Returns

Result<Money>

Exceptions

ArgumentNullException

Thrown when other is null.

Sum(IEnumerable<Money>)

Sums a collection of Money values. All values must share the same currency.

public static Result<Money> Sum(IEnumerable<Money> values)

Parameters

values IEnumerable<Money>

The monetary values to sum.

Returns

Result<Money>

Success with the total, or failure if the collection is empty, contains mixed currencies, or overflows.

Remarks

Empty input fails because Money carries currency and there is no canonical "zero currency". Callers that have a meaningful currency for the empty case should use Sum(IEnumerable<Money>, Money) and pass a zero-amount Money in the desired currency as fallback.

Sum(IEnumerable<Money>, Money)

Sums a collection of Money values, returning fallback when the collection is empty.

public static Result<Money> Sum(IEnumerable<Money> values, Money fallback)

Parameters

values IEnumerable<Money>

The monetary values to sum.

fallback Money

The value to return when values is empty.

Returns

Result<Money>

Success with the total when values is non-empty. Success with fallback when values is empty. Failure if the non-empty values contain mixed currencies or the addition overflows.

Examples

// Empty line items return USD 0 instead of failing.
var total = Money.Sum(order.LineItems.Select(li => li.Subtotal), Money.Zero("USD").Unwrap());

Remarks

fallback is consulted only when values is empty. When values is non-empty the result currency is inferred from the first element, exactly as in Sum(IEnumerable<Money>) — the fallback's currency is irrelevant in that case. This mirrors Sum(IEnumerable<MonetaryAmount>)'s empty-collection-yields-zero ergonomic for currencies that Money can represent.

ToString()

Returns a string representation of the money amount with currency.

public override string ToString()

Returns

string

TryCreate(decimal, string, string?)

Creates a Money instance with the specified amount and currency code.

public static Result<Money> TryCreate(decimal amount, string currencyCode, string? fieldName = null)

Parameters

amount decimal

The monetary amount.

currencyCode string

ISO 4217 currency code (e.g., "USD", "EUR"); case-insensitive.

fieldName string

Optional name of the money field itself; component errors are reported beneath it.

Returns

Result<Money>

Result containing the Money instance or validation errors.

Remarks

fieldName names the Money value as a whole, and validation errors are reported against a nested JSON Pointer under it — one segment per component, matching the serialized shape { "amount": …, "currency": … }. Passing "price" therefore yields /price/amount for amount failures and /price/currency for currency failures, so a client can bind each error to the field that produced it. When fieldName is omitted the pointers are /amount and /currency. A fieldName that is already a JSON Pointer (starts with '/') is composed onto rather than escaped, so "/items/0/price" yields /items/0/price/amount.

Use TryCreate(decimal, string, string?, string?) instead when the amount and currency arrive as sibling fields of a flat payload rather than as a nested object.

Currency validation is delegated to TryCreate(string?, string?) and is syntactic only (three ASCII letters per ISO 4217 format; input is case-insensitive, stored uppercase via ToUpperInvariant). The ISO 4217 active-code list is not enforced — values like XXX, XTS, or ZZZ satisfy the format and will be accepted. Applications that need to restrict to a specific set of currencies (e.g., those a payment processor supports, or excluding ISO reserved/test codes) should layer an allow-list at the application boundary before calling TryCreate(decimal, string, string?).

TryCreate(decimal, string, string?, string?)

Creates a Money instance whose amount and currency are validated against independently named fields, for payloads that carry the two as siblings rather than as a nested object.

public static Result<Money> TryCreate(decimal amount, string currencyCode, string? amountFieldName, string? currencyFieldName)

Parameters

amount decimal

The monetary amount.

currencyCode string

ISO 4217 currency code (e.g., "USD", "EUR"); case-insensitive.

amountFieldName string

Field carrying the amount; defaults to amount when null. An empty string explicitly targets the document root.

currencyFieldName string

Field carrying the currency; defaults to currency when null. An empty string explicitly targets the document root.

Returns

Result<Money>

Result containing the Money instance or validation errors.

Remarks

Use this overload when the incoming document looks like { "price": 10, "currency": "XX" }. The nested-pointer composition performed by TryCreate(decimal, string, string?) would report such a failure at /price/currency — a location that does not exist in the payload. Each argument accepts a simple property name or a full JSON Pointer.

Zero(string)

Creates a zero-value Money instance for the specified currency.

public static Result<Money> Zero(string currencyCode = "USD")

Parameters

currencyCode string

ISO 4217 currency code. Defaults to "USD".

Returns

Result<Money>

A Result<TValue> wrapping the constructed Money. Returns the success branch when currencyCode is a supported currency, and the failure branch when it is not — the currency code is validated at construction time, so this method is not a plain Money factory. Consumers must unwrap the result (typically with .TryGetValue(...) or .Match(...)) before using the value.