-
Notifications
You must be signed in to change notification settings - Fork 14
DeveloperQuickStart
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.
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.
Latest binaries can be downloaded from Releases.
For NuGet users, the NMoneys package is available in the official live feed.
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.
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.
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.
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();
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);
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);
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));
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 >
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
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"
string quantity = "EUR 42.75";
Money parsed = Money.Parse(quantity); // 42,75 €
Extension methods are provided as shortcuts for creation of money and currency instances:
3m.Gbp();
1000m.Pounds();
CurrencyIsoCode.NOK.ToMoney(3m);