CurrenCSharp 1.0.0
dotnet add package CurrenCSharp --version 1.0.0
NuGet\Install-Package CurrenCSharp -Version 1.0.0
<PackageReference Include="CurrenCSharp" Version="1.0.0" />
<PackageVersion Include="CurrenCSharp" Version="1.0.0" />
<PackageReference Include="CurrenCSharp" />
paket add CurrenCSharp --version 1.0.0
#r "nuget: CurrenCSharp, 1.0.0"
#:package CurrenCSharp@1.0.0
#addin nuget:?package=CurrenCSharp&version=1.0.0
#tool nuget:?package=CurrenCSharp&version=1.0.0
CurrenCSharp
Introduction
CurrenCSharp is a .NET library for handling monetary values.
It provides:
- a
Moneytype (amount + currency), - a
Wallettype (a collection of monetary values, potentially across multiple currencies), - a
ContextedMoney/ContextedWalletpair 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 exampleEUR 47.11).Wallet: a collection ofMoneyvalues, potentially across multiple currencies.ExchangeRateContext: exchange rates for a base currency at a reference timestamp.ContextedMoney/ContextedWallet: aMoney/Walletbound to anExchangeRateContextvia.In(context), required for conversion or cross-currency comparison.IExchangeRateProvider: asynchronous source forExchangeRateContextvalues (latest or historical).
Feature Examples
The examples below mirror the end-to-end walkthrough in
example/CurrenCSharp.Example/Program.cs. They assumeusing CurrenCSharp;andusing 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:
Moneyis a value type.default(Money)is intentionally invalid — accessingCurrencythrowsNoCurrencyException. Always construct vianew Money(amount, currency)orMoney.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 | Versions 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on CurrenCSharp:
| Package | Downloads |
|---|---|
|
CurrenCSharp.Currencies
ISO 4217 currency catalog for CurrenCSharp with predefined currencies and lookup helpers. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 90 | 9/15/2026 |
| 1.0.0-rc.1 | 608 | 4/28/2026 |