Currency Currency
Currency Currency
DocFX + Singulink = ♥

Search Results for

    Money Bags

    A money bag is a collection that holds one MonetaryValue per currency. It is the type to reach for whenever an amount can be spread across currencies: a multi-currency account balance, a shopping cart that accepts several currencies, or a report total. This guide covers the four bag types, how values combine, and the operations they support.

    The four types

    Type Mutable Ordered by currency code
    MoneyBag Yes No
    SortedMoneyBag Yes Yes
    ImmutableMoneyBag No No
    ImmutableSortedMoneyBag No Yes

    All four implement IReadOnlyMoneyBag, the mutable ones implement IMoneyBag, and the immutable ones implement IImmutableMoneyBag, whose mutating methods return a new bag. The unsorted bags are hash-based and slightly faster; the sorted bags enumerate in a stable order, which matters for display and for deterministic output. IsSorted reports which kind a bag is when only the interface is known.

    Creating Bags

    Bags support collection expressions, collection initializers, constructors and factory methods:

    MoneyBag bag = [new(100m, "USD"), new(50m, "EUR")];
    var bag2 = new MoneyBag { new(100m, "USD"), new(50m, "EUR") };
    var bag3 = new MoneyBag(values);
    
    ImmutableSortedMoneyBag snapshot = [new(1m, "CAD"), new(2m, "USD")];
    var snapshot2 = ImmutableMoneyBag.CreateRange(values);
    
    IReadOnlyMoneyBag readOnly = [new(1m, "CAD")];   // creates a MoneyBag
    

    Every bag is bound to a CurrencyRegistry, available through Registry, which defaults to Default. Adding a value whose currency is not in the registry throws ArgumentException. Pass a registry to the constructor or factory to restrict a bag to a specific set of currencies. See Currencies and Registries.

    The MoneyCollectionExtensions methods such as ToImmutableMoneyBag convert between bag types and from any sequence of values.

    Adding and Subtracting

    Bags combine values by currency. Adding a value in a currency the bag already holds adds to the existing amount, and subtracting works the same way. A currency stays in the bag when its amount reaches zero until it is explicitly removed or trimmed:

    var bag = new MoneyBag();
    
    bag.Add(new MonetaryValue(100m, "USD"));
    bag.Add(25m, "USD");                    // USD 125
    bag.Subtract(125m, "USD");              // USD 0, still present
    bag.AddRange(otherBag);
    bag.SubtractRange(refunds);
    
    bag.TrimZeroAmounts();                  // removes USD 0
    

    Default values are ignored when added or subtracted, which lets accumulators start from Default without special cases.

    SetValue and SetAmount replace a currency's amount instead of combining with it. Remove and RemoveAll drop currencies entirely.

    Immutable bags expose the same operations returning new instances:

    var balance = ImmutableMoneyBag.Create(new MonetaryValue(100m, "USD"));
    var updated = balance.Add(50m, "EUR").Subtract(10m, "USD");
    

    Reading Values

    The indexer returns the value for a currency, or the default value when the bag does not contain it, so lookups never throw for a missing currency:

    var usd = bag["USD"];                 // USD 125
    var jpy = bag["JPY"];                 // default value, IsDefault is true
    
    if (bag.TryGetValue("EUR", out var eur)) { }
    if (bag.TryGetAmount("EUR", out decimal amount)) { }
    
    bag.Count;                            // number of currencies
    bag.Currencies;                       // the currencies present
    bag.ContainsCurrency("USD");
    bag.Contains(new MonetaryValue(125m, "USD"));
    

    Bags enumerate as MonetaryValue sequences, so LINQ works directly. Because equality across currencies is safe and comparison is not, group or filter by currency before ordering by amount.

    Transforming Values

    TransformAmounts and TransformValues apply a function to every value in the bag. The overloads that take a function returning a nullable amount remove the currency when the function returns null:

    bag.TransformAmounts(amount => amount * 1.13m);              // apply tax to everything
    bag.TransformValues(value => value.Amount > 0 ? value.Amount : null); // drop non-positive values
    

    Round and RoundToCash round every value according to its own currency's policy, which is the usual last step after applying rates or percentages. See Rounding and Allocation.

    Formatting

    Bags format as a comma-separated list of their values using the same format strings as MonetaryValue, with an optional ! prefix that omits zero amounts. See Formatting.

    Next Steps

    Continue with these related articles:

    • Monetary Values - The values a bag contains.
    • Currencies and Registries - Restricting a bag to a registry.
    • Formatting - Bag format strings.
    © Singulink. All rights reserved.