Skip to content

DeveloperQuickStart

Daniel Gonzalez Garcia edited this page Jan 28, 2024 · 10 revisions

Target Platform

NMoneys is a .Net library. It is developed in C# in Visual Studio targeting the 4.0 version of the framework. In theory, it should run in every platform that supports the .Net Framework as it is "pure" .Net (no explicit Interop, P/Invoke or OS function calls). But we expect the community of users to tell us in which platform they use the library and whether they are experiencing problems or not.

Build sources

Building the library is as simple as getting the latest source code from the repository and from a Powershell prompt that contains (a netcore-able version of) .Net framework MsBuild.exe in the path, invoke:

path_to_source_code\build.ps1

and the built binaries and XML documentation can be copied from path_to_source_code\release into the folder that contains dependencies for your solution.

Get the binaries

Latest binaries can be downloaded from Releases.

For NuGet users, the NMoneys package is available in the official live feed.

NMoneys Nuget

NuGet version

Strong naming

I took the decision of not strong-naming any of the assemblies, as I think strong-naming causes more trouble than anything with NuGet.
In quite a few years of .NET development I have been able to dodge the need to strong name my assemblies; I suggest you do the same.

Documentation from Microsoft suggests to revert to strong-naming only when strictly necessary and those scenarios are rare enough to not provide general support.
However, even for those edge cases, there is a workaround: use the StrongNamer Nuget package in those strong-named projects of yours that reference any of the NMoneys packages.

Binary signed versions of NMoneys assemblies are no longer to be supplied via Releases.
A key file is still provided with the repository in case you want to build from source and sign the resulting assemblies, but the aforementioned work-around is the way to go.

Use the Library

Currencies

Obtain an instance

Currency cannot be instantiated through a constructor. An instance has to be retrieved using either one of the static fields:

Currency euro = Currency.Eur;

Or one of the static creation methods:

Currency cad = Currency.Get(CurrencyIsoCode.CAD);
Currency australianDollar = Currency.Get("AUD");
Currency euro = Currency.Get(CultureInfo.GetCultureInfo("es-ES"));

Currency itMightNotBe;
string isoSymbol;
CurrencyIsoCode isoCode;
CultureInfo culture;
bool wasFound = Currency.TryGet(isoSymbol, out itMightNotBe);
wasFound = Currency.TryGet(isoCode, out itMightNotBe);
wasFound = Currency.TryGet(culture, out itMightNotBe);

The Get() creation method may throw if an invalid currency identifier is supplied. To prevent exceptions use the TryGet() methods.

Using a currency

There is little to do with the type, it only contain properties such as symbol, ISO code , ISO symbol and properties that drive how a monetary quantity with this currency might look like.

Dealing with codes

ISO 4417 codes are implemented as enumeration values. That means that the standard class Enum can be used. But the behaviors of this class can be "surprising" sometimes. In order to be consistent, we can use the Currency.Code class to obtain CurrencyIsoCode instances:

CurrencyIsoCode usd = Currency.Code.Cast(840);
CurrencyIsoCode eur = Currency.Code.Parse("eur");

CurrencyIsoCode? maybeAud;
Currency.Code.TryCast(36, out maybeAud);
Currency.Code.TryParse("036", out maybeAud);

A number of extension methods are also provided to get more information from CurrencyIsoCode:

short thirtySix = CurrencyIsoCode.AUD.NumericCode();
string USD = CurrencyIsoCode.USD.AlphabeticCode();
string zeroThreeSix = CurrencyIsoCode.AUD.PaddedNumericCode();

Moneys

Get an instance

Money, unlike currencies, can be directly instantiated.

var threeDollars = new Money(3m, Currency.Usd);
var twoandAHalfPounds = new Money(2.5m, CurrencyIsoCode.GBP);
var tenEuro = new Money(10m, "EUR");
var thousandWithMissingCurrency = new Money(1000m);

Comparisons

Moneys of the same currency can be compared and its value equality asserted:

int isPositive = new Money(3m, Currency.Nok).CompareTo(new Money(2m, CurrencyIsoCode.NOK));
bool isFalse = new Money(2m) > new Money(3m);
bool areEqual = new Money(1m, Currency.Xxx).Equals(new Money(1m, Currency.Xxx));
bool areNotEqual = new Money(2m) != new Money(5m);

Arithmetic Operations

Simple arithmetic operations can be performed on Money instances:

var three = new Money(3m);
var two = new Money(2m);
Money five = three.Plus(two);
Money oneOwed = two - three;
Money one = oneOwed.Abs();

Arihtmetic operations between instances of the same currency are open to the user via Perform() methods:

Money alsoTwo = two.Perform(ten, (amt1, amt2) => amt1 % amt2);
Money three = new Money(3.9m).Perform(amt => Math.Floor(amt));

Allocations

Complex allocation logic is performed via the Allocate() methods. For example, to perform split allocations:

Allocation fair = 40m.Eur().Allocate(4);
// fair.IsComplete --> true
// fair.Remainder --> 0€
// fair --> < 10€, 10€, 10€ >

Allocation unfair = 40m.Eur().Allocate(3, RemainderAllocator.LastToFirst);
// unfair.IsComplete --> false
// unfair.Remainder --> 0€
// unfair --> < 13.33€, 13.33€, 13.33€, 13.34€ >

Pro-rated allocations are also possible:

var foemmelsConundrumSolution = .05m.Usd().Allocate(new RatioCollection(.3m, 0.7m));
// foemmelsConundrumSolution --> < $0.02, $0.03 >

var anotherFoemmelsConundrumSolution = .05m.Usd().Allocate(new RatioCollection(.3m, 0.7m), RemainderAllocator.LastToFirst);
// anotherFoemmelsConundrumSolution --> < $0.01, $0.04 >

Make Change

Change can be made for a Money instance given a set of currency denominations (Denomination).
Let's imagine we pay $1 for an article that costs ¢63. How could one get that money back?

Money moneyBack = 1m.Usd().Minus(.37m.Usd()); // ¢63
Denomination[] usCoins = 
{
	new Denomination(.25m), // quarters
	new Denomination(.10m), // dimes
	new Denomination(.05m), // nickels
	new Denomination(.01m)  // pennies
};
uint seventyThree = moneyBack.CountWaysToMakeChange(usCoins);
// --> 73 ways to make change of ¢63

// optimal for canonical systems such as US coins
ChangeSolution change = moneyBack.MakeChange(usCoins);
// --> six coins: 2 quarters, 1 dime, 3 pennies

OptimalChangeSolution sameChange = moneyBack.MakeOptimalChange(usCoins);
// --> six coins as well: 2 quarters, 1 dime, 3 pennies
var nonCanonicalDenominations = new[]
{
	new Denomination(.25m), // quarters
	new Denomination(.10m), // dimes
	new Denomination(.05m), // nickels
	new Denomination(.01m), // pennies
	new Denomination(.21m)  // ?
};
// sub-optimal in this case
ChangeSolution subOptimal = moneyBack.MakeChange(nonCanonicalDenominations);
// --> six coins: 2 quarters, 1 dime, 3 pennies

// optimal solution
OptimalChangeSolution optimal = moneyBack.MakeOptimalChange(nonCanonicalDenominations);
// --> three coins: 3 of .21

Formatting

Of course a Money instance can be formatted in several ways:

string s = tenEuro.ToString(); // default formatting "10,00 €"
42.75m.Eur().AsQuantity(); // compact-parseable formatting "EUR 42.75"
s = threeDollars.ToString("N"); // format applied to currency "3.00"
s = threeDollars.ToString(CultureInfo.GetCultureInfo("es-ES")); // format provider used "3,00 €", better suited for countries with same currency and different number formatting
s = threeDollars.Format("{1} {0:00.00}"); // formatting with placeholders for currency symbol and amount "$ 03.00"
s = new Money(1500, Currency.Eur).Format("{1} {0:#,#.00}"); // rich formatting "€ 1.500,00"

Parsing

string quantity = "EUR 42.75";
Money parsed = Money.Parse(quantity); // 42,75 €

Extensions

Extension methods are provided as shortcuts for creation of money and currency instances:

3m.Gbp();
1000m.Pounds();
CurrencyIsoCode.NOK.ToMoney(3m);