MoneyBag Class
Definition
Aggregates monetary balances across multiple currencies. Immutable; every mutator returns a new instance.
public sealed class MoneyBag : IEquatable<MoneyBag>, IEnumerable<Money>, IEnumerable
- Inheritance
-
MoneyBag
- Implements
- Inherited Members
- Extension Methods
Remarks
MoneyBag is the multi-currency aggregate used to model portfolios, ledger totals that span currencies, FX positions, or any other context where amounts in different currencies must be tracked together but not silently merged. Zero balances are pruned automatically on every operation.
Enumeration yields one Money per non-zero currency, in ISO-code lexicographic order so the iteration is stable and reproducible across runs.
JSON serialization ships in the companion Bodu.Financial.Serialization.Json package; the type carries no
[JsonConverter] attribute, so register the financial converters on the target JsonSerializerOptions
via its AddFinancialJsonConverters extension.
using Bodu.Financial;
using Bodu.Financial.Currencies;
// Every mutator returns a new bag; same-currency amounts are summed automatically.
MoneyBag portfolio = MoneyBag.Empty
.Add(new Money(1_000m, CurrencyCode.USD))
.Add(new Money(250m, CurrencyCode.USD)) // folds into the USD slot -> 1,250.00 USD
.Add<EUR>(new Money<EUR>(500m)); // typed overload
// Merge two bags, summing per currency.
MoneyBag combined = portfolio.Combine(new MoneyBag([new Money(200m, CurrencyCode.GBP)]));
// Read a single currency back, typed and null-safe.
Money<USD>? usd = combined.GetBalance<USD>(); // 1,250.00 USD
int currencies = combined.Count; // 3 (USD, EUR, GBP)
Constructors
MoneyBag()
Initializes a new instance of the MoneyBag class.
public MoneyBag()
MoneyBag(IEnumerable<Money>)
Initializes a new instance of the MoneyBag class from a sequence of Money balances, summing amounts with the same currency.
public MoneyBag(IEnumerable<Money> balances)
Parameters
balancesIEnumerable<Money>The starting balances.
Exceptions
- ArgumentNullException
balancesis null.- ArgumentException
A balance carries no currency (default-initialised).
Fields
Empty
The shared empty bag instance.
public static readonly MoneyBag Empty
Field Value
Properties
Balances
Gets a read-only view of the balance map keyed by CurrencyCode, in ISO-code lexicographic order.
public IReadOnlyDictionary<CurrencyCode, decimal> Balances { get; }
Property Value
- IReadOnlyDictionary<CurrencyCode, decimal>
The immutable balance map. Because the backing store is an ImmutableSortedDictionary<TKey, TValue>, the view is genuinely read-only and is returned without allocating a wrapper.
Count
Gets the number of distinct currencies carrying a non-zero balance.
public int Count { get; }
Property Value
- int
The number of currency slots.
IsEmpty
Gets a value indicating whether this bag carries no balances.
public bool IsEmpty { get; }
Property Value
Methods
Add(Money)
Returns a new bag with amount added to the balance for its currency.
public MoneyBag Add(Money amount)
Parameters
amountMoneyThe amount to add.
Returns
- MoneyBag
The updated bag.
Remarks
The bag is a settlement-precision container: the incoming amount is rounded to its currency's registered minor units (banker's rounding) before it is folded into the balance, so an explicit-scale unit price settles on entry. Settle high-precision amounts deliberately - via RoundToMoney(MonetaryContext?) - when a different rounding rule is required.
Exceptions
- ArgumentException
amounthas no currency (default-initialised).
Add<TCurrency>(Money<TCurrency>)
Returns a new bag with the typed amount added to the balance for its currency.
public MoneyBag Add<TCurrency>(Money<TCurrency> amount) where TCurrency : ICurrency
Parameters
amountMoney<TCurrency>The amount to add.
Returns
- MoneyBag
The updated bag.
Type Parameters
TCurrencyThe currency type.
Combine(MoneyBag)
Returns a new bag containing the union of this bag's balances and other's, summing
per-currency amounts.
public MoneyBag Combine(MoneyBag other)
Parameters
otherMoneyBagThe other bag to combine.
Returns
- MoneyBag
The combined bag.
Exceptions
- ArgumentNullException
otheris null.
ConvertToWithAudit<TTarget>(IDatedRateProvider, DateOnly, RateLookupOptions?, MoneyBagConversionRoundingPolicy)
Converts the entire bag to a single target currency and returns the audit metadata describing which observation was used for each per-currency line.
public MoneyBagConversionAudit<TTarget> ConvertToWithAudit<TTarget>(IDatedRateProvider rates, DateOnly date, RateLookupOptions? options, MoneyBagConversionRoundingPolicy policy = MoneyBagConversionRoundingPolicy.SumRawThenRound) where TTarget : ICurrency
Parameters
ratesIDatedRateProviderThe dated provider that resolves each rate.
dateDateOnlyThe valuation date supplied to every lookup.
optionsRateLookupOptionsThe lookup rules supplied to every lookup.
policyMoneyBagConversionRoundingPolicyThe rounding policy applied during aggregation.
Returns
- MoneyBagConversionAudit<TTarget>
A MoneyBagConversionAudit<TTarget> containing the aggregated total and one MoneyBagConversionLine per source currency. Lines for balances already in
TTargetreport a null rate (identity pass-through).
Type Parameters
TTargetThe destination currency type.
Examples
using Bodu.Financial;
using Bodu.Financial.Currencies;
using Bodu.Financial.ExchangeRates;
MoneyBag ledger = MoneyBag.Of(
Money.From(1450.00m, CurrencyCode.AUD),
Money.From(370.75m, CurrencyCode.USD));
MoneyBagConversionAudit<AUD> audit = ledger.ConvertToWithAudit<AUD>(
rates, new DateOnly(2024, 3, 15), RateLookupOptions.PreviousWithin(3));
// audit.Total is the aggregated Money<AUD>; each line explains one balance.
foreach (MoneyBagConversionLine line in audit.Lines)
{
// line.SourceIsoCode, line.SourceAmount, line.RawConvertedAmount, and
// line.Rate (null for the AUD balance - identity pass-through).
}
Exceptions
- ArgumentNullException
ratesis null.- ArgumentOutOfRangeException
policyis not a defined value.- KeyNotFoundException
No rate is available for one of the bag's currencies under
options.
ConvertTo<TTarget>(IDatedRateProvider, DateOnly, RateLookupOptions?)
Converts the entire bag to a single target currency by resolving every non-target balance through a dated provider at the supplied valuation date and options. Uses the SumRawThenRound policy.
public Money<TTarget> ConvertTo<TTarget>(IDatedRateProvider rates, DateOnly date, RateLookupOptions? options = null) where TTarget : ICurrency
Parameters
ratesIDatedRateProviderThe dated provider that resolves each rate.
dateDateOnlyThe valuation date supplied to every lookup.
optionsRateLookupOptionsThe lookup rules supplied to every lookup.
Returns
- Money<TTarget>
The aggregated Money<TCurrency> total.
Type Parameters
TTargetThe destination currency type.
Exceptions
- ArgumentNullException
ratesis null.- KeyNotFoundException
No rate is available for one of the bag's currencies under
options.
ConvertTo<TTarget>(IDatedRateProvider, DateOnly, RateLookupOptions?, MoneyBagConversionRoundingPolicy)
Converts the entire bag to a single target currency through a dated provider, using the supplied rounding policy.
public Money<TTarget> ConvertTo<TTarget>(IDatedRateProvider rates, DateOnly date, RateLookupOptions? options, MoneyBagConversionRoundingPolicy policy) where TTarget : ICurrency
Parameters
ratesIDatedRateProviderThe dated provider that resolves each rate.
dateDateOnlyThe valuation date supplied to every lookup.
optionsRateLookupOptionsThe lookup rules supplied to every lookup.
policyMoneyBagConversionRoundingPolicyThe rounding policy applied during aggregation.
Returns
- Money<TTarget>
The aggregated Money<TCurrency> total.
Type Parameters
TTargetThe destination currency type.
Exceptions
- ArgumentNullException
ratesis null.- ArgumentOutOfRangeException
policyis not a defined value.- KeyNotFoundException
No rate is available for one of the bag's currencies under
options.
ConvertTo<TTarget>(IRateProvider)
Converts the entire bag to a single target currency by applying the supplied rate provider to every non-target balance and aggregating into the destination precision per SumRawThenRound.
public Money<TTarget> ConvertTo<TTarget>(IRateProvider rates) where TTarget : ICurrency
Parameters
ratesIRateProviderThe provider that returns the rate for each (from, to) pair.
Returns
- Money<TTarget>
The aggregated Money<TCurrency> total.
Type Parameters
TTargetThe destination currency type.
Exceptions
- ArgumentNullException
ratesis null.- KeyNotFoundException
ratescannot supply a rate for one of the bag's currencies.- InvalidOperationException
A rate returned by
ratesis zero or negative.
ConvertTo<TTarget>(IRateProvider, MoneyBagConversionRoundingPolicy)
Converts the entire bag to a single target currency using the explicit rounding policy.
public Money<TTarget> ConvertTo<TTarget>(IRateProvider rates, MoneyBagConversionRoundingPolicy policy) where TTarget : ICurrency
Parameters
ratesIRateProviderThe provider that returns the rate for each (from, to) pair.
policyMoneyBagConversionRoundingPolicyThe rounding policy applied during aggregation.
Returns
- Money<TTarget>
The aggregated Money<TCurrency> total.
Type Parameters
TTargetThe destination currency type.
Exceptions
- ArgumentNullException
ratesis null.- ArgumentOutOfRangeException
policyis not a defined value.- KeyNotFoundException
ratescannot supply a rate for one of the bag's currencies.- InvalidOperationException
A rate returned by
ratesis zero or negative.
ConvertTo<TTarget>(Func<string, string, decimal>)
Converts the entire bag to a single target currency using a delegate-based rate lookup.
public Money<TTarget> ConvertTo<TTarget>(Func<string, string, decimal> rateLookup) where TTarget : ICurrency
Parameters
Returns
- Money<TTarget>
The aggregated Money<TCurrency> total.
Type Parameters
TTargetThe destination currency type.
Exceptions
- ArgumentNullException
rateLookupis null.- InvalidOperationException
A rate returned by
rateLookupis zero or negative.
ConvertTo<TTarget>(Func<string, string, decimal>, MoneyBagConversionRoundingPolicy)
Converts the entire bag to a single target currency using a delegate-based rate lookup and the supplied rounding policy.
public Money<TTarget> ConvertTo<TTarget>(Func<string, string, decimal> rateLookup, MoneyBagConversionRoundingPolicy policy) where TTarget : ICurrency
Parameters
rateLookupFunc<string, string, decimal>A delegate that returns the rate for a (from, to) pair.
policyMoneyBagConversionRoundingPolicyThe rounding policy applied during aggregation.
Returns
- Money<TTarget>
The aggregated Money<TCurrency> total.
Type Parameters
TTargetThe destination currency type.
Exceptions
- ArgumentNullException
rateLookupis null.- ArgumentOutOfRangeException
policyis not a defined value.- InvalidOperationException
A rate returned by
rateLookupis zero or negative.
Equals(MoneyBag?)
Determines whether two bags carry the same balances.
public bool Equals(MoneyBag? other)
Parameters
otherMoneyBagThe other bag.
Returns
Equals(object?)
Determines whether the specified object is equal to the current object.
public override bool Equals(object? obj)
Parameters
objobjectThe object to compare with the current object.
Returns
FromBalances(IEnumerable<Money>)
Creates a MoneyBag from a sequence of balances, summing amounts with the same ISO code.
public static MoneyBag FromBalances(IEnumerable<Money> balances)
Parameters
balancesIEnumerable<Money>The starting balances.
Returns
- MoneyBag
The constructed bag.
Exceptions
- ArgumentNullException
balancesis null.- ArgumentException
A balance carries no ISO code (default-initialised).
GetBalance(CurrencyCode)
Returns the balance for the currency identified by code, or null when the
bag has no entry for that currency.
public Money? GetBalance(CurrencyCode code)
Parameters
codeCurrencyCodeThe currency identifying the balance.
Returns
GetBalance(CurrencyInfo)
Returns the balance for currency, or null when the bag has no entry for
that currency.
public Money? GetBalance(CurrencyInfo currency)
Parameters
currencyCurrencyInfoThe currency metadata identifying the balance.
Returns
Exceptions
- ArgumentNullException
currencyis null.- InvalidOperationException
currency's ISO code does not correspond to any CurrencyCode member.
GetBalance<TCurrency>()
Returns the typed balance for TCurrency, or null when absent.
public Money<TCurrency>? GetBalance<TCurrency>() where TCurrency : ICurrency
Returns
Type Parameters
TCurrencyThe currency type.
GetEnumerator()
Enumerates the non-zero balances in ISO-code lexicographic order.
public IEnumerator<Money> GetEnumerator()
Returns
- IEnumerator<Money>
An enumerator over Money entries.
GetHashCode()
Serves as the default hash function.
public override int GetHashCode()
Returns
- int
A hash code for the current object.
Of(params Money[])
Creates a MoneyBag from the supplied balances, summing amounts with the same ISO code.
public static MoneyBag Of(params Money[] balances)
Parameters
balancesMoney[]The starting balances.
Returns
- MoneyBag
The constructed bag.
Exceptions
- ArgumentNullException
balancesis null.- ArgumentException
A balance carries no ISO code (default-initialised).
Subtract(Money)
Returns a new bag with amount subtracted from the balance for its currency.
public MoneyBag Subtract(Money amount)
Parameters
amountMoneyThe amount to subtract.
Returns
- MoneyBag
The updated bag.
Subtract<TCurrency>(Money<TCurrency>)
Returns a new bag with the typed amount subtracted.
public MoneyBag Subtract<TCurrency>(Money<TCurrency> amount) where TCurrency : ICurrency
Parameters
amountMoney<TCurrency>The amount to subtract.
Returns
- MoneyBag
The updated bag.
Type Parameters
TCurrencyThe currency type.
TryGetBalance(CurrencyCode, out Money)
Attempts to retrieve the balance for the currency identified by code.
public bool TryGetBalance(CurrencyCode code, out Money balance)
Parameters
codeCurrencyCodeThe currency identifying the balance.
balanceMoneyWhen this method returns true, the runtime-tagged balance.
Returns
Operators
operator +(MoneyBag, Money)
Adds a single Money to a bag.
public static MoneyBag operator +(MoneyBag left, Money right)
Parameters
Returns
- MoneyBag
The updated bag.
operator +(MoneyBag, MoneyBag)
Combines two bags.
public static MoneyBag operator +(MoneyBag left, MoneyBag right)
Parameters
Returns
- MoneyBag
The combined bag.
operator ==(MoneyBag?, MoneyBag?)
Determines whether two bags are equal.
public static bool operator ==(MoneyBag? left, MoneyBag? right)
Parameters
Returns
operator !=(MoneyBag?, MoneyBag?)
Determines whether two bags differ.
public static bool operator !=(MoneyBag? left, MoneyBag? right)
Parameters
Returns
operator -(MoneyBag, Money)
Subtracts a Money from a bag.
public static MoneyBag operator -(MoneyBag left, Money right)
Parameters
Returns
- MoneyBag
The updated bag.
Explicit Interface Implementations
IEnumerable.GetEnumerator()
Returns an enumerator that iterates through a collection.
IEnumerator IEnumerable.GetEnumerator()
Returns
- IEnumerator
An IEnumerator object that can be used to iterate through the collection.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |