Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

NMoneys, a Money Value Object implementation in .NET

4.85/5 (13 votes)
20 May 2011CPOL10 min read 38.9K   407  
NMoneys is an implementation of the Money Value Object to support representing moneys in the currencies defined in the ISO 4217 standard, for the .NET platform.

NMoneys Logo

Background

Although the .NET Framework Base Class Library (BCL) offers a great deal of artifacts, it does not offer a good way of representing monetary quantities in different currencies. It does offer, instead, numeric types and a way to format numeric values according to different formats. Problem lies in that those formatting rules are mixed with concepts like cultures, languages, dates, calendars, and so on, to the extent to become difficult and, in some cases, impossible to represent a simple concept such as "one Canadian dollar" or "ten-and-the-half Zambian kwacha".

On top of the confusing mix of concepts, it happens that, sometimes, regional or formatting information is wrong: incorrect currency for a given country, outdated information, missing countries, and so on. The .NET Framework, being as big and widely used, has a rate of releasing fixes that is not very dynamic (cough, cough) and if one's application relies on the Framework information for displaying monetary quantities for missing or incorrect currencies, one is pretty much out of luck.

Furthermore, not only the implementation does not follow the ISO standard for currencies, but that formatting information can be changed by the user, making consistency across versions, machines, and updates a very challenging task.

Some other libraries might exist and one might go with hand-rolled classes, but in some cases the API is not as nice to work with as it could be, or they are pretty much not maintained at all.

Lack of support and consistency, having a need, and a pair of hands to type, I created the NMoneys library to solve my problems and the problems of some of the .NET developers out there. It is Open Source because it is neither rocket science nor the single most valuable asset in a company's utility belt. Besides, surprise, surprise, I do not know everything about currencies and monetary quantities. But with the support of the community, that can be solved, given that contributions are made to the project.

Some of my colleagues, myself, and, hopefully, someone else who I do not know yet, have already used the library before this article was published, meaning that, at least it is useful to solve somebody's problems.

Using the code

I am a strong believer that expressive unit tests are the best form of documentation that exists. I also think they are as good as any other way of showing off the abilities of NMoneys. Those unit tests are NUnit tests and can be executed by your favorite test runner.

All code for snippets (and more that is not published for brevity) will be included in the demo project (Visual Studio 2010, .NET 4.0).

The code for the demo project can also be browsed from the horse's mouth in the project web site. Likewise, the latest version of the code for NMoneys library can be easily accessed here.

ISO currency codes

From The International Organization for Standardization (ISO): "This International Standard specifies the structure for a three-letter alphabetic code and an equivalent three-digit numeric code for the representation of currencies and funds". Maintenance of the standard is carried out by the ISO 4217 Maintenance Agency.

What all this bureaucracy talk means is that there is an international organism that standardizes currencies and their alphabetic and numeric codes. That is, they reflect the changes in legal tender used by a number of entities (as they call them) or countries/institutions (so that everyone can understand).

NMoneys is not focused on the list of entities (countries) at all, but in the list of valid currencies at this current moment in time. This list establishes which currencies are valid, and which are not recognized. Yes, I know, it is a standard, that involves a lot of politics and yadi yadi dah, but it is the most reliable list of currencies at hand.

Bottom line is: there are a lot of currencies, currencies are dynamic (new ones are used and old ones are deprecated), and the .NET Framework does not support all of them or it is simply wrong in some other cases.

Currency codes in NMoneys

In NMoneys, a given currency code is represented as a value of the enumeration CurrencyIsoCode.

C#
[Test]
public void currency_codes_are_modeled_as_enums_named_after_its_ISO_alphabetic_code()
{
    CurrencyIsoCode usDollars = CurrencyIsoCode.USD;
    CurrencyIsoCode euro = CurrencyIsoCode.EUR;
    CurrencyIsoCode danishKrona = CurrencyIsoCode.DKK;
    CurrencyIsoCode noCurrency = CurrencyIsoCode.XXX;
}

[Test]
public void currency_codes_have_their_ISO_numeric_value()
{
    Assert.That((short)CurrencyIsoCode.USD, Is.EqualTo(840));
    Assert.That((short)CurrencyIsoCode.EUR, Is.EqualTo(978));
    Assert.That((short)CurrencyIsoCode.DKK, Is.EqualTo(208));
    Assert.That((short)CurrencyIsoCode.XXX, Is.EqualTo(999));
}

[Test]
public void less_common_currencies_are_also_modeled_as_long_as_they_are_approved_by_ISO()
{
    CurrencyIsoCode platinum = CurrencyIsoCode.XPT;
    CurrencyIsoCode yemeniRial = CurrencyIsoCode.YER;
}

[Test]
public void recently_deprecated_currencies_are_also_present()
{
    var estonianKroon = CurrencyIsoCode.EEK;
    Assert.That(estonianKroon.AsAttributeProvider(), Has.Attribute<ObsoleteAttribute>());
}

As you can see from the snippet, the complete ISO standard is implemented, with alphabetic and numeric codes, including less common currencies and recently deprecated ones, but those are marked with the ObsoleteAttribute to clearly highlight them as deprecated.

Currencies

CurrencyIsoCode is the way to represent a code for a given currency present in the ISO standard. But the amount of interesting behaviors that a value of an enumeration can offer is very limited. Clearly, another type to do that job for us is needed. Entering the Currency type.

Obtain an instance

Only a limited amount of currencies is possible. With codes being the CurrencyIsoCode enumeration, limiting the number of "instances" is provided by the framework itself, but with Currency, a flyweight pattern-like interface is provided.

"Popular" currencies get a direct static access.

C#
[Test]
public void popular_currency_instances_can_be_obtained_from_static_accessors()
{
    Assert.That(Currency.Usd, Is.Not.Null.And.InstanceOf<Currency>());
    Assert.That(Currency.Eur, Is.Not.Null.And.InstanceOf<Currency>());
    Assert.That(Currency.Dkk, Is.Not.Null.And.InstanceOf<Currency>());
    Assert.That(Currency.Xxx, Is.Not.Null.And.InstanceOf<Currency>());
}

There is no static access for all currencies. Instead, a factory method Currency.Get() accepts a variety of inputs to obtain the instance of the currency.

C#
[Test]
public void currency_instances_can_be_obtained_from_its_code_enum()
{
    Assert.That(Currency.Get(CurrencyIsoCode.ZAR), Is.InstanceOf<Currency>());
}

[Test]
public void currency_instances_can_be_obtained_from_its_code_string()
{
    Assert.That(Currency.Get("eur"), Is.Not.Null);
    Assert.That(Currency.Get("EUR"), Is.Not.Null);
}

[Test]
public void currency_instances_can_be_obtained_from_a_CultureInfo_instance()
{
    CultureInfo swedish = CultureInfo.GetCultureInfo("sv-SE");
    Assert.That(Currency.Get(swedish), Is.EqualTo(Currency.Sek));
}

[Test]
public void currency_instances_can_be_obtained_from_a_RegionInfo_instance()
{
    RegionInfo spain = new RegionInfo("es");
    Assert.That(Currency.Get(spain), Is.EqualTo(Currency.Eur));
}

If the input is somehow unsafe, exceptions can be saved using another factory method Currency.TryGet() that follows the well-known TryParse pattern.

C#
[Test]
public void currency_instances_can_be_obtained_with_a_try_do_pattern()
{
    Currency currency;
    Assert.That(Currency.TryGet(CurrencyIsoCode.ZAR, out currency), Is.True);
    Assert.That(currency, Is.Not.Null.And.InstanceOf<Currency>());

    Assert.That(Currency.TryGet("zar", out currency), Is.True);
    Assert.That(currency, Is.Not.Null.And.InstanceOf<Currency>());
    Assert.That(Currency.TryGet(CultureInfo.GetCultureInfo("en-ZA"), out currency), Is.True);
    Assert.That(currency, Is.Not.Null.And.InstanceOf<Currency>());

    Assert.That(Currency.TryGet(new RegionInfo("ZA"), out currency), Is.True);
    Assert.That(currency, Is.Not.Null.And.InstanceOf<Currency>());
}

[Test]
public void TryGet_does_not_throw_if_currency_cannot_be_found()
{
    Currency currency;
    CurrencyIsoCode notDefined = (CurrencyIsoCode)0;
    Assert.That(Currency.TryGet(notDefined, out currency), Is.False);
    Assert.That(currency, Is.Null);
    Assert.That(Currency.TryGet("notAnIsoCode", out currency), Is.False);
    Assert.That(currency, Is.Null);

    CultureInfo neutralCulture = CultureInfo.GetCultureInfo("da");
    Assert.That(Currency.TryGet(neutralCulture, out currency), Is.False);
    Assert.That(currency, Is.Null);
}

What's with deprecated currencies

Currencies come and go. New currencies become mainstream and some become deprecated. But that does not mean we cannot use them anymore. Data from when those currencies were not deprecated may exist and that common scenario needs to be supported.

C#
[Test]
public void instances_of_deprecated_currencies_can_still_be_obtained()
{
    Currency deprecated = Currency.Get("EEK");
    Assert.That(deprecated, Is.Not.Null.And.InstanceOf<Currency>());
    Assert.That(deprecated.IsObsolete, Is.True);
}

And yet there are some problem domains in which the use of deprecated currencies is not acceptable. For those scenarios, a notification system in the shape of static events is provided.

C#
[Test]
public void whenever_a_deprecated_currency_is_obtained_an_event_is_raised()
{
    bool called = false;
    CurrencyIsoCode obsolete = CurrencyIsoCode.XXX;
    EventHandler<ObsoleteCurrencyEventArgs> callback = (sender, e) =>
    {
        called = true;
        obsolete = e.Code;
    };

    try
    {
        Currency.ObsoleteCurrency += callback;
        Currency.Get("EEK");
        Assert.That(called, Is.True);
        Assert.That(obsolete.ToString(), Is.EqualTo("EEK"));
        Assert.That(obsolete.AsAttributeProvider(), Has.Attribute<ObsoleteAttribute>());
    }
    // DO unsubscribe from global events whenever listening isnot needed anymore
    finally
    {
        Currency.ObsoleteCurrency -= callback;
    }
}

Note that the snippet is very clear about the issue. Be aware that static event handlers are one of the most common sources of memory leaks. Please do unsubscribe from them when no more notifications are needed. You have been warned.

Much ado about LINQ

Of course we do love LINQ. It has made our lives so much... happier? Well, it has been a great addition to the languages. Our contribution to the cause comes in a way to retrieve all currencies in the shape of an enumerable collection. Have fun reducing, mapping, and aggregating on all the currencies of the world...

C#
[Test]
public void all_currencies_can_be_obtained_and_linq_operators_applied()
{
    Assert.That(Currency.FindAll(), Is.Not.Null.And.All.InstanceOf<Currency>());
    var allCurrenciesWithoutMinorUnits = 
        Currency.FindAll().Where(c => c.SignificantDecimalDigits == 0);
    Assert.That(allCurrenciesWithoutMinorUnits, Is.Not.Empty.And.Contains(Currency.Jpy));
}

What's in a currency

We have seen how a Currency instance can be obtained. But what's inside it that makes it appealing?

C#
[Test]
public void whats_in_a_currency_anyway()
{
    Currency euro = Currency.Eur;

    Assert.That(euro.IsObsolete, Is.False);
    Assert.That(euro.IsoCode, Is.EqualTo(CurrencyIsoCode.EUR));
    Assert.That(euro.IsoSymbol, Is.EqualTo("EUR"));
    Assert.That(euro.NativeName, Is.EqualTo("Euro"), 
      "capitalized in the default instance");
    Assert.That(euro.NumericCode, Is.EqualTo(978));
    Assert.That(euro.PaddedNumericCode, Is.EqualTo("978"), 
      "a string of 3 characters containing the numeric code and zeros if needed");
    Assert.That(euro.Symbol, Is.EqualTo("€"));
}

And living in a world in which "angly bracket" languages of all kinds play a role, there are some chosen currencies that have the privilege to be represented in those languages in a peculiar way. That peculiar way of being displayed is provided by a CharacterReference property.

C#
[Test]
public void some_currencies_have_an_character_reference_for_angly_bracket_languages()
{
    Currency qatariRial = Currency.Get(CurrencyIsoCode.QAR);
    CharacterReference reference = qatariRial.Entity;
    Assert.That(reference, Is.Not.Null.And.Property("IsEmpty").True,
        "the Rial does not have an reference, but a 'null' object");

    Currency euro = Currency.Euro;
    reference = euro.Entity;
    Assert.That(reference, Is.Not.Null.And.Property("IsEmpty").False, 
                "the euro, does");
    Assert.That(reference.Character, Is.EqualTo("€"));
    Assert.That(reference.CodePoint, Is.EqualTo(8364));
    Assert.That(reference.EntityName, Is.EqualTo("&euro;"));
    Assert.That(reference.EntityNumber, Is.EqualTo("&#8364;"));
    Assert.That(reference.SimpleName, Is.EqualTo("euro"));
}

What to do with a Currency?

By far, the most interesting and key behavior of Currency is formatting numeric quantities. But, wouldn't it make sense to show how to format numeric quantities by presenting a special case of them: the monetary quantity?

Moneys

Currency implements the behaviors of a given currency, mainly formatting. But the way to univocally represent a monetary quantity in a given currency, differentiating it from another quantity in another currency, has not been seen yet. Such a representation is the Money structure.

Obtain an instance

As opposed to Currency, which only offers a limited number of instances to be created, there can be many instances of Money around. It follows the semantics of numeric types and as such it has been implemented as a struct instead of a reference type. The most common way of obtaining an instance is using one of its multiple constructors.

C#
[Test]
public void a_Money_represents_a_monetary_quantity()
{
    new Money(10m, Currency.Dollar);        // Money --> tenDollars
    new Money(2.5m, CurrencyIsoCode.EUR);   // Money --> twoFiftyEuros
    new Money(10m, "JPY");        // Money --> tenYen
    new Money();                            // Money --> zeroWithNoCurrency
}

Interactions with the environment (such as the current culture) are made explicit. In the following snippet, the NUnit attribute SetCultureAttribute is used to control which culture the test is run under (in this case, Danish).

C#
[Test, SetCulture("da-DK")]
public void environment_dependencies_are_explicit()
{
    Money fiveKrona = Money.ForCurrentCulture(5m);
    Assert.That(fiveKrona.CurrencyCode, Is.EqualTo(CurrencyIsoCode.DKK));

    Money currencyLessMoney = new Money(1);
    Assert.That(currencyLessMoney.CurrencyCode, Is.EqualTo(CurrencyIsoCode.XXX));

    Money zeroEuros = Money.ForCulture(0m, CultureInfo.GetCultureInfo("es-ES"));
    Assert.That(zeroEuros.CurrencyCode, Is.EqualTo(CurrencyIsoCode.EUR));
}

There is another way to create money instances: using a set of extension methods for brevity. The main scenario for this way of creating instances is unit testing. When testing, a short and expressive way to create objects for your scenarios is a big help.

C#
[Test]
public void moneys_can_be_quickly_created_for_testing_scenarios_with_extension_methods()
{
    // Money --> threeNoCurrencies
    3m.Xxx();
    3m.ToMoney();

    // Money --> threeAndAHalfAustralianDollars
    3.5m.Aud();
    3.5m.ToMoney(Currency.Aud);
    3.5m.ToMoney(CurrencyIsoCode.AUD);
    CurrencyIsoCode.AUD.ToMoney(3.5m);
}

Parsing

Yet another way to create instances of Money is to parse a given string. This feature is only so useful, because the expected currency of the input needs to be passed beforehand, since there are many currencies that use the same display symbol.

C#
[Test]
public void moneys_can_be_parsed_to_a_known_currency()
{
    Assert.That(Money.Parse("$1.5", Currency.Dollar), 
       isMoneyWith(1.5m, CurrencyIsoCode.USD), "one-and-the-half dollars");
    Assert.That(Money.Parse("10 €", Currency.Euro), 
       isMoneyWith(10m, CurrencyIsoCode.EUR), "ten euros");

    Assert.That(Money.Parse("kr -100", Currency.Dkk), 
       isMoneyWith(-100m, CurrencyIsoCode.DKK), "owe hundrede kroner");
    Assert.That(Money.Parse("(¤1.2)", Currency.None), 
       isMoneyWith(-1.2m, CurrencyIsoCode.XXX), "owe one point two, no currency");
}

Major and minor units

It is very common amongst currencies to have a major unit and a minor one. One clear example is, amongst others, the Sterling Pound. With that currency, the major unit is the pound itself, whereas the minor unit is the penny.

For such common scenarios, there is a way to create money instances based on the amount of major units.

C#
[Test]
public void what_is_with_this_Major_thing()
{
    Assert.That(Money.ForMajor(234, Currency.Gbp), isMoneyWith(234, CurrencyIsoCode.GBP),
        "instance created from the major units, in this case the Pound");

    Assert.That(3m.Pounds().MajorAmount, Is.EqualTo(3m), 
                "for whole amounts is the quantity");
    Assert.That(3.7m.Pounds().MajorAmount, Is.EqualTo(3m), 
                "for fractional amounts is the number of pounds");
    Assert.That(0.7m.Pounds().MajorAmount, Is.EqualTo(0m), 
                "for fractional amounts is the number of pounds");

    Assert.That(3m.Pounds().MajorIntegralAmount, Is.EqualTo(3L), 
                "for whole amounts is the non-fractional quantity");
    Assert.That(3.7m.Pounds().MajorIntegralAmount, Is.EqualTo(3L), 
                "for fractional amounts is the number of pounds");
    Assert.That(0.7m.Pounds().MajorIntegralAmount, Is.EqualTo(0L), 
                "for fractional amounts is the number of pounds");
}

And a way to create instances based on the amount of minor units.

C#
[Test]
public void what_is_with_this_Minor_thing()
{
    Assert.That(Currency.Pound.SignificantDecimalDigits, Is.EqualTo(2), 
                "pounds have pence, which is a hundreth of the major unit");

    Assert.That(Money.ForMinor(234, Currency.Gbp), isMoneyWith(2.34m, CurrencyIsoCode.GBP),
        "234 pence is 2.34 pounds");
    Assert.That(Money.ForMinor(50, Currency.Gbp), isMoneyWith(0.5m, CurrencyIsoCode.GBP),
        "fifty pence is half a pound");
    Assert.That(Money.ForMinor(-5, Currency.Gbp), isMoneyWith(-0.05m, CurrencyIsoCode.GBP),
        "you owe me five pence, but keep them");

    Assert.That(3m.Pounds().MinorAmount, Is.EqualTo(300m), "three pounds is 300 pence");
    Assert.That(.07m.Pounds().MinorAmount, Is.EqualTo(7m), 
                "for fractional amounts, the minor unit prevails");
    Assert.That(0.072m.Pounds().MinorAmount, Is.EqualTo(7m), 
                "tenths of pence are discarded");

    Assert.That(3m.Pounds().MinorIntegralAmount, Is.EqualTo(300L), 
                "three pounds is 300 pence");
    Assert.That(.07m.Pounds().MinorIntegralAmount, Is.EqualTo(7L), 
                "for fractional amounts, the minor unit prevails");
    Assert.That(0.072m.Pounds().MinorIntegralAmount, Is.EqualTo(7L), 
                "tenths of pence are discarded");
}

Some currencies adopt the schema of having a minor unit which is the hundredth of the major unit. In other currencies, minor units are the thousandth of the major unit. And other currencies have no minor units at all. Of course, all these cases are supported.

What's in a money

We have seen how a Money instance can be created. Why would anyone instantiate it? Is it for the data it provides?

C#
[Test]
public void what_is_in_a_money()
{
    Money threeCads = new Money(3m, "CAD");

    Assert.That(threeCads.Amount, Is.EqualTo(3m));
    Assert.That(threeCads.CurrencyCode, Is.EqualTo(CurrencyIsoCode.CAD));
    Assert.That(threeCads.HasDecimals, Is.False);
    Assert.That(threeCads.IsNegative(), Is.False);
    Assert.That(threeCads.IsNegativeOrZero(), Is.False);
    Assert.That(threeCads.IsPositive(), Is.True);
    Assert.That(threeCads.IsPositiveOrZero(), Is.True);
    Assert.That(threeCads.IsZero(), Is.False);
}

That, and the information about major and minor amounts is all interesting stuff. But... I am sure people want to do things with their money.

What to do with money?

That is an interesting question. But for the sake of this article, the answer will be focused towards the behaviors of Money objects.

To start with, being quantities of some kind, it makes a lot of sense that moneys can be compared. Otherwise, bragging about how red is our bank account would be impossible...

C#
[Test]
public void moneys_can_be_compared()
{
    Assert.That(3m.Usd().Equals(CurrencyIsoCode.USD.ToMoney(3m)), Is.True);
    Assert.That(3m.Usd() != CurrencyIsoCode.USD.ToMoney(3m), Is.False);
    Assert.That(3m.Usd().CompareTo(CurrencyIsoCode.USD.ToMoney(5m)), Is.LessThan(0));
    Assert.That(3m.Usd() < CurrencyIsoCode.USD.ToMoney(5m), Is.True);
}

Instances of different currencies cannot be measured against each other blindly, just as "planes" cannot be compared to "apples" (at least for most useful purposes). And since none of the goals of NMoneys is providing rate exchange services, moneys with different currencies simply cannot be compared.

C#
[Test]
public void comparisons_only_possible_if_they_have_the_same_currency()
{
    Assert.That(3m.Usd().Equals(3m.Gbp()), Is.False);
    Assert.That(3m.Usd() != CurrencyIsoCode.GBP.ToMoney(3m), Is.True);

    Assert.That(() => 3m.Usd().CompareTo(CurrencyIsoCode.GBP.ToMoney(5m)), 
                Throws.InstanceOf<DifferentCurrencyException>());
    Assert.That(() => 3m.Usd() < CurrencyIsoCode.GBP.ToMoney(5m), 
                Throws.InstanceOf<DifferentCurrencyException>());
}

Monetary quantities can be formatted for their display in several ways.

C#
[Test]
public void moneys_are_to_be_displayed()
{
    Assert.That(10.536m.Eur().ToString(), Is.EqualTo("10,54 €"), 
                "default currency formatting according to instance's currency");
    Assert.That(3.2m.Usd().ToString("N"), Is.EqualTo("3.20"), 
                "alternative formatting according to instance's currency");
}

A common scenario is currencies that are used in different countries and those countries have different ways of representing monetary quantities. That common scenario is covered by using a different format provider than the one provided by the currency of the instance.

C#
[Test]
public void using_different_styles_for_currencies_taht_span_multiple_countries()
{
    Assert.That(3000.5m.Eur().ToString(), Is.EqualTo("3.000,50 €"), "default euro formatting");

    // in French the group separator is neither the dot or the space
    CultureInfo french = CultureInfo.GetCultureInfo("fr-FR");
    string threeThousandAndTheHaldInFrench = string.Format("3{0}000,50 €", 
                        french.NumberFormat.CurrencyGroupSeparator);
    Assert.That(3000.5m.Eur().ToString(french), 
                Is.EqualTo(threeThousandAndTheHaldInFrench));
}

Richer formatting capabilities are also possible.

C#
[Test]
public void more_complex_formatting()
{
    Assert.That(3m.Usd().Format("{0:00.00} {2}"), Is.EqualTo("03.00 USD"), 
                "formatting placeholders for code and amount");
    Assert.That(2500m.Eur().Format("> {1} {0:#,#.00}"), Is.EqualTo("> € 2.500,00"), 
                "rich amount formatting");
}

Displaying moneys is important. But another very important thing one needs to do with a monetary quantity is performing some arithmetic operations.

C#
[Test]
public void moneys_are_to_be_operated_with_arithmetic_operators()
{
    Money fivePounds = 2m.Pounds().Plus(3m.Pounds());
    Assert.That(fivePounds, isMoneyWith(5m, CurrencyIsoCode.GBP));

    Money fiftyPence = 3m.Pounds() - 2.5m.Pounds();
    Assert.That(fiftyPence, isMoneyWith(.5m, CurrencyIsoCode.GBP));

    Money youOweMeThreeEuros = -3m.Eur();
    Assert.That(youOweMeThreeEuros, isMoneyWith(-3m, CurrencyIsoCode.EUR));

    Money nowIHaveThoseThreeEuros = youOweMeThreeEuros.Negate();
    Assert.That(nowIHaveThoseThreeEuros, isMoneyWith(3m, CurrencyIsoCode.EUR));

    Money youOweMeThreeEurosAgain = -nowIHaveThoseThreeEuros;
    Assert.That(youOweMeThreeEurosAgain, isMoneyWith(-3m, CurrencyIsoCode.EUR));
}

And since a complete set of operations to be performed on moneys is difficult to foresee, there are some simple extensibility points.

C#
[Test]
public void basic_arithmetic_operations_can_be_extended()
{
    Money halfMyDebt = -60m.Eur().Perform(amt => amt / 2);
    Assert.That(halfMyDebt, isMoneyWith(-30m, CurrencyIsoCode.EUR));

    Money convolutedWayToCancelDebt = (-50m).Eur().Perform(-1m.Eur(),
         (amt1, amt2) => decimal.Multiply(amt1, decimal.Negate(amt2)) - amt1);
    Assert.That(convolutedWayToCancelDebt, 
                isMoneyWith(decimal.Zero, CurrencyIsoCode.EUR));
}

As with comparisons, it mostly makes sense to perform binary operations on moneys with the same currency.

C#
[Test]
public void binary_operations_only_possible_if_they_have_the_same_currency()
{
    Assert.That(() => 2m.Gbp().Minus(3m.Eur()), 
                               Throws.InstanceOf<DifferentCurrencyException>());
    Assert.That(() => 2m.Cad() + 3m.Aud(), Throws.InstanceOf<DifferentCurrencyException>());
    Assert.That(() => 3m.Usd().Perform(3m.Aud(), (x, y) => x + y), 
                               Throws.InstanceOf<DifferentCurrencyException>());
}

Most times it is more fun when two are involved, but a single money can do quite a few things on its own.

C#
[Test]
public void several_unary_operations_can_be_performed()
{
    Assert.That(3m.Xxx().Negate(), isMoneyWith(-3m), "-1 * amount");
    Assert.That((-3m).Xxx().Abs(), isMoneyWith(3m), "|amount|");

    Money twoThirds = new Money(2m / 3);
    Assert.That(twoThirds.Amount, Is.Not.EqualTo(0.66m), 
                "not exactly equals as it has more decimals");
    Assert.That(twoThirds.TruncateToSignificantDecimalDigits().Amount, Is.EqualTo(0.66m), 
                "XXX has two significant decimals");

    Money fractional = 123.456m.ToMoney();
    Assert.That(fractional.Truncate(), isMoneyWith(123m), "whole amount");

    Assert.That(.5m.ToMoney().RoundToNearestInt(), isMoneyWith(0m));
    Assert.That(.599999m.ToMoney().RoundToNearestInt(), isMoneyWith(1m));
    Assert.That(1.5m.ToMoney().RoundToNearestInt(), isMoneyWith(2m));
    Assert.That(1.4999999m.ToMoney().RoundToNearestInt(), isMoneyWith(1m));

    Assert.That(.5m.ToMoney().RoundToNearestInt(MidpointRounding.ToEven), 
                isMoneyWith(0m), "closest even number is 0");
    Assert.That(.5m.ToMoney().RoundToNearestInt(MidpointRounding.AwayFromZero), 
                isMoneyWith(1m), "closest number away from zero is 1");
    Assert.That(1.5m.ToMoney().RoundToNearestInt(MidpointRounding.ToEven), 
                isMoneyWith(2m), "closest even number is 2");
    Assert.That(1.5m.ToMoney().RoundToNearestInt(MidpointRounding.AwayFromZero), 
                isMoneyWith(2m), "closest number away from zero is 2");

    Assert.That(2.345m.Usd().Round(), isMoneyWith(2.34m), "round to two decimals");
    Assert.That(2.345m.Jpy().Round(), isMoneyWith(2m), "round to no decimals");
    Assert.That(2.355m.Usd().Round(), isMoneyWith(2.36m), "round to two decimals");
    Assert.That(2.355m.Jpy().Round(), isMoneyWith(2m), "round to no decimals");

    Assert.That(2.345m.Usd().Round(MidpointRounding.ToEven), isMoneyWith(2.34m));
    Assert.That(2.345m.Usd().Round(MidpointRounding.AwayFromZero), isMoneyWith(2.35m));
    Assert.That(2.345m.Jpy().Round(MidpointRounding.ToEven), isMoneyWith(2m));
    Assert.That(2.345m.Jpy().Round(MidpointRounding.AwayFromZero), isMoneyWith(2m));

    Assert.That(123.456m.ToMoney().Floor(), isMoneyWith(123m));
    Assert.That((-123.456m).ToMoney().Floor(), isMoneyWith(-124m));
}

Wrapping it up

I had several goals with this article.

The main one was to raise the awareness of the NMoneys library so that more people can benefit from its usage, and more importantly, more people can contribute.

Contributions can come in several shapes, but mainly in two ways: new features for the library and more accurate information for the currency information. Both will be more than welcome.

The other goal was to show some scenarios that make the library compelling to use. Helping the potential user to become a real one.

I hope any (or all) of the goals is achieved. If not, I, for one, had lots of fun sharing this with you, guys.

History

  • 08-May-2011 - Initial version.
  • 10-May-2011 - Added external links to source code.
  • 11-May-2011 - Included library code within demo project.
  • 20-May-2011 - Fixed typo in article description.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)