CurrenCSharp.Currencies 1.0.0

dotnet add package CurrenCSharp.Currencies --version 1.0.0
                    
NuGet\Install-Package CurrenCSharp.Currencies -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CurrenCSharp.Currencies" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CurrenCSharp.Currencies" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="CurrenCSharp.Currencies" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CurrenCSharp.Currencies --version 1.0.0
                    
#r "nuget: CurrenCSharp.Currencies, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CurrenCSharp.Currencies@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CurrenCSharp.Currencies&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=CurrenCSharp.Currencies&version=1.0.0
                    
Install as a Cake Tool

CurrenCSharp

Introduction

CurrenCSharp is a .NET library for handling monetary values.

It provides:

  • a Money type (amount + currency),
  • a Wallet type (a collection of monetary values, potentially across multiple currencies),
  • a ContextedMoney / ContextedWallet pair that attaches an exchange-rate context for conversion and cross-currency comparison.

The library supports arithmetic, comparison, conversion, and distribution of amounts. For exchange rates you implement IExchangeRateProvider and plug in your own source.

Installation

Install the core package:

dotnet add package CurrenCSharp

Install the optional ISO 4217 package (predefined currencies):

dotnet add package CurrenCSharp.Currencies

Core Concepts

  • Currency: an ISO 4217 currency definition (AlphaCode, NumericCode, MinorUnits).
  • Money: one monetary amount in one currency (for example EUR 47.11).
  • Wallet: a collection of Money values, potentially across multiple currencies.
  • ExchangeRateContext: exchange rates for a base currency at a reference timestamp.
  • ContextedMoney / ContextedWallet: a Money / Wallet bound to an ExchangeRateContext via .In(context), required for conversion or cross-currency comparison.
  • IExchangeRateProvider: asynchronous source for ExchangeRateContext values (latest or historical).

Feature Examples

The examples below mirror the end-to-end walkthrough in example/CurrenCSharp.Example/Program.cs. They assume using CurrenCSharp; and using CurrenCSharp.Currencies;.

1. Ambient Default Currency

CurrenC.UseDefaultCurrency sets an ambient default currency for the current async scope. The returned IDisposable restores the previous default on dispose (scopes must be disposed in LIFO order).

using var defaultCurrencyScope = CurrenC.UseDefaultCurrency(Iso4217.EUR);

While the scope is active, Currency.Default and Money.Zero() resolve to EUR.

2. Creating Money

Money default_zero = Money.Zero();            // EUR 0.00 (uses default currency)
Money usd_zero    = Money.Zero(Iso4217.USD);  // USD 0.00

Money eur_47_11 = new(47.11m, Iso4217.EUR);
Money usd_47_11 = new(47.11m, Iso4217.USD);
Money chf_23_42 = new(23.42m, Iso4217.CHF);

Money is an immutable record struct and exposes convenience properties IsZero, IsPositive, and IsNegative.

Note: Money is a value type. default(Money) is intentionally invalid — accessing Currency throws NoCurrencyException. Always construct via new Money(amount, currency) or Money.Zero(currency).

3. Creating Wallet

Wallet empty      = Wallet.Empty;
Wallet simple     = Wallet.Of(eur_47_11);
Wallet multiple   = Wallet.Of(eur_47_11, usd_47_11, chf_23_42);
Wallet collection = Wallet.Of([eur_47_11, usd_47_11, chf_23_42]);

A Wallet aggregates money per currency automatically. It is enumerable (IEnumerable<Money>) and provides structural equality.

4. Exchange-Rate Providers

Implement IExchangeRateProvider to plug in any rate source (REST API, database, cache, ...):

public interface IExchangeRateProvider
{
    Task<ExchangeRateContext> GetLatestAsync(CancellationToken cancellationToken = default);
    Task<ExchangeRateContext> GetHistoricalAsync(DateTimeOffset date, CancellationToken cancellationToken = default);
}
IExchangeRateProvider provider = new ExampleExchangeRateProvider();
DateTimeOffset exchangeRateDate = new(new DateTime(2020, 1, 1), TimeSpan.Zero);

ExchangeRateContext latestExchangeRates = await provider.GetLatestAsync();
ExchangeRateContext historicalExchangeRates = await provider.GetHistoricalAsync(exchangeRateDate);

See example/CurrenCSharp.Example/ExampleExchangeRateProvider.cs for a minimal reference implementation.

5. Binding Money and Wallet to a Context

Use .In(context) to attach an exchange-rate context and enable conversion and cross-currency comparison.

ContextedMoney  eur_47_11_with_latest_rates     = eur_47_11.In(latest);
ContextedMoney  eur_47_11_with_historical_rates = eur_47_11.In(historical);

ContextedWallet wallet_with_latest_rates     = collection.In(latest);
ContextedWallet wallet_with_historical_rates = collection.In(historical);

6. Currency Conversion

ContextedMoney.Convert converts the amount into another currency using the bound context:

Money converted_eur_in_usd_with_latest_rates     = eur_47_11_with_latest_rates.Convert(Iso4217.USD);     // USD 51.41
Money converted_eur_in_usd_with_historical_rates = eur_47_11_with_historical_rates.Convert(Iso4217.USD); // USD 42.08

Rounding is controlled via ConversionOptions:

Money custom = eur_47_11_with_latest_rates.Convert(
    Iso4217.USD,
    new ConversionOptions(
        RoundResult: true,
        RoundingMode: MidpointRounding.AwayFromZero,
        Scale: new Scale(4)));

ExchangeRateContext automatically handles inverse rates and cross rates via the base currency.

7. Wallet Totals

ContextedWallet.Total() sums the wallet into a single Money. Without a target currency it uses the wallet's resolved currency (the single currency in the wallet, or the ambient default).

Money total_of_wallet_in_default_currency_with_latest_rates = latest_wallet.Total();            // EUR 140.10
Money total_of_wallet_in_usd_with_historical_rates          = latest_wallet.Total(Iso4217.USD); // USD 152.87

8. Arithmetic Operators

8.1 On Money (same currency required)
Money   negate   = -eur_47_11;            // EUR -47.11
Money   sum      = eur_47_11 + eur_23_42; // EUR 70.53
Money   diff     = eur_47_11 - eur_23_42; // EUR 23.69
Money   multiply = eur_47_11 * 2;         // EUR 94.22
decimal quote    = eur_47_11 / eur_23_42; // 2.01

Mixing currencies in +, -, or / throws DifferentCurrencyException.

8.2 On Wallet
Wallet negate      = -collection;            // EUR -47.11, USD -47.11, CHF -47.11
Wallet addition    = collection + eur_23_42; // EUR 70.53, USD 47.11, CHF 47.11
Wallet subtraction = collection - eur_23_42; // EUR 23.69, USD 47.11, CHF 47.11
Wallet multiply    = collection * 3;         // EUR 141.33, USD 141.33, CHF 141.33
Wallet division    = collection / 2;         // EUR 23.56, USD 23.56, CHF 23.56

Adding or subtracting Money updates the matching currency bucket; adding two wallets merges them per currency. Scalar * / / apply to every entry.

9. Comparison Operators

Equality and ordering operators are defined for Money, Wallet, and their context-aware counterparts. Operators that cross currencies require a context-bound operand.

9.1 Money vs. Money (same currency)
bool isEqual          = eur_47_11 == eur_47_11; // True
bool isNotEqual       = eur_47_11 != eur_23_42; // True
bool isGreater        = eur_47_11 >  eur_23_42; // True
bool isGreaterOrEqual = eur_47_11 >= eur_23_42; // True
bool isLess           = eur_47_11 <  eur_23_42; // False
bool isLessOrEqual    = eur_47_11 <= eur_23_42; // False
9.2 Money vs. ContextedMoney (currencies may differ)
bool a = usd_47_11     == latest_money; // False
bool b = latest_money  >  usd_47_11;    // True
bool c = usd_47_11     <= latest_money; // True
9.3 Money vs. ContextedWallet (currencies may differ)
bool a = eur_47_11     == latest_wallet; // False
bool b = latest_wallet >  eur_47_11;     // True
bool c = eur_47_11     <= latest_wallet; // True
9.4 Wallet vs. ContextedMoney (currencies may differ)
bool a = collection    == latest_money;  // False
bool b = collection    >  latest_money;  // True
bool c = latest_money  <= collection;    // True
9.5 Wallet vs. ContextedWallet (currencies may differ)
bool a = collection    == latest_wallet; // True
bool b = collection    >= latest_wallet; // True
bool c = latest_wallet <= collection;    // True

10. Distributing Amounts

Money.Distribute splits an amount into parts without losing minor units. Remaining units are allocated to the largest ratios (ties broken by index).

10.1 Equal parts
var parts = eur_47_11.Distribute(3);
// EUR 15.71, EUR 15.70, EUR 15.70
10.2 Weighted parts via Ratio
var parts = eur_47_11.Distribute(3, 1, 2, 0);
// EUR 23.56, EUR 7.85, EUR 15.70, EUR 0.00

Ratio is a non-negative value type with implicit conversion from decimal. The sum of ratios must be greater than zero.

Strongly Typed Currency Codes

AlphaCode and NumericCode validate ISO 4217 codes and offer Parse / TryParse:

AlphaCode   alpha   = AlphaCode.Parse("EUR");
NumericCode numeric = NumericCode.Parse("978");

Currency eur = new(alpha, numeric, 2);

Optional ISO 4217 Package

CurrenCSharp.Currencies ships predefined Currency instances for supported current ISO 4217 currencies and selected historical currencies. Current currencies are fields on Iso4217, historical currencies are fields on the nested class Iso4217.Historical. Both sets use the same lookup cache:

using CurrenCSharp.Currencies;

Currency eur            = Iso4217.EUR;
Currency foundByAlpha   = Iso4217.FindByAlphaCode("USD");
Currency foundByNumeric = Iso4217.FindByNumericCode(840);

Currency deutscheMark          = Iso4217.Historical.DEM;
Currency deutscheMarkByAlpha   = Iso4217.FindByAlphaCode("DEM");
Currency deutscheMarkByNumeric = Iso4217.FindByNumericCode(276);
Money restant = new(100m, deutscheMark);

The field and both DEM lookups return the same Currency instance. Historical currencies are deliberately kept apart from current ones: Iso4217.DEM does not exist, only Iso4217.Historical.DEM.

The historical catalog is intentionally incomplete. It includes only codes in the Wikipedia/SIX historical intersection whose alpha code, numeric code, and ISO minor units are unambiguous and documented. Codes with reused numeric values (for example PES/PEN) or missing data are excluded. The data sources, the 2026-01-01 SIX snapshot, and the exact inclusion and exclusion lists are documented in the data audit.

FindByAlphaCode and FindByNumericCode throw InvalidOperationException when the code is not in the supported catalog. A catalog entry supplies no exchange rates. Historical conversion still requires an explicit context and a caller- provided IExchangeRateProvider.

License

See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 71 9/15/2026
1.0.0-rc.1 601 4/28/2026