Introduction

I have long wanted to write an article for CodeProject as I have used the articles and resources here many times. Then as part of a recent application I realized how lacking .NET is for currency support, don't get me wrong, there are many "pieces" but the glue for all items is missing, so this article is a response to that.

In this article (my first of many I hope), I'd like to cover some of the new features I found very useful from VS 2005 and .NET 2, including Class Designer, Operator Overloading, Generics, Testing, Code Coverage and "developer" documentation. With that in mind, let me say that this article is for .NET 2 only as some of the features I've used are not available in the earlier versions. You can order for the BETA 2 DVD from Microsoft, if you don't have a copy.

Background

The application in question deals with Money (initially not in different currencies, but then that could change) and even though .NET supplies a Decimal datatype to make the arithmetic easier catering to double rounding issues (I won't cover this here), there is no way to ensure that two Decimal values are of the same currency type, which would cause an error which is hard to find if they are added together. There is also no easy way to convert them into a currency type, or to display them in the correct output format, which made me ask "So how do you display 23.45 in the Danish currency?".

Money project

I started by first creating a new Class project, and in the new Class designer I "visually" designed the structure stub (needed a Value Type). Being a programmer, I hate using paper and pen so this is a great feature for designing applications. When done, I created the test stubs from the resulting class. Here is the Money project that I came up with:

Money.vb

To solve the currency storage issues, I added a property for the currency; this is represented by the Region property. This is Globalization.RegionInfo type which has been around since .NET v1.0. It contains all the information I needed to supply currency information to other sources, like the Currency Name (Danish Krone for example), the Currency Symbol (kr) and the ISO currency symbol (DKK). I also needed formatting information for output and the region doesn't provide that, so the Culture property (Globalization.CultureInfo type) was added as well. I'm not sure if it's a good idea to add this during the creation of the Money variable instance, but I have not done much performance testing yet. I supplied a few constructors for the Money type to make it easier to specify the Currency type. For example, if the region is not included, the money value will be expressed in the current region. However, when a region is supplied, that region will be set and the correct culture is retrieved for that region.

First trick

It is easy to get Region from Culture:

_region = New RegionInfo(_culture.LCID)

Or, to get the Culture from region (I'm not sure whether this is totally correct, but it works for the tests I ran. Can anyone confirm?).

I needed all the standard operators (+, -, *, /) and even added a few extras (\, Mod and Sum). The important part was to ensure that two money objects with different currencies (regions) are not added, subtracted, etc., so in each operator I performed a check for the region currency type and raised a RegionMismatchException if they don't match.

Equality was also needed, so I included all the different operators for that (=, <>, >=, <=), I then noticed that as I added =, the VS2005 IDE informed me that I had to add matching <> operator (nice feature). And if you turn on Code Analysis, you will learn that you need to include Equals, which in turn needs GetHashCode... this is getting bigger, bigger... I won't go into the code here, as I have commented it if you wish to read through it, and this brings me to:

Second trick

Pressing ''' (3 single quotes) in VB IDE over a property or method will automatically enter an empty comment stub which is used for XML documentation.

Having entered the comment, I soon had to use the function in code and was pleasantly surprised when the info tip popped up with MY comments.

The second last thing I needed to add was the formatting functions for output. The easiest way was to implement the IFormattable interface giving access to the ToString methods. These three methods gave me a few options and I'll explain these in a bit more detail as it took me a while to get my head around the MSDN documentation on these functions:

ToString() - The default method most often used. Specifically used to output the Money value in the currency format of the money instance (not the PCs current culture). This is where the Culture value is used:

Return _value.ToString("C", _culture.NumberFormat)

The hard work is done by the Decimal formatter, "C" tells it to use the Currency format and _culture.NumberFormat (a FormatProvider) tells it how to format the currency value (i.e. what decimal character to use, the number of figures in a grouping etc.), saves me from doing it.

PublicOverloadsFunction ToString(ByVal formatProvider As System.IFormatProvider) AsString - Again, I'll just pass on the complexity to the Decimal formatting logic, this time passing it a supplied FormatProvider and let it work out what to do. This is the function that I use when I want to convert the value to Words, but more on that later.

ReturnString.Format(formatProvider, "{0}", _value)

The final function is the one added automatically when I added the IFormattable interface to the structure.

PublicOverloadsFunction ToString(ByVal format AsString, ByVal formatProvider As System.IFormatProvider) AsStringImplements System.IFormattable.ToString - This is a combination of both the other two, the format string and the FormatProvider are supplied.

The code snippet above will produce 1,234.45 in English invariant culture even though it's a money value, because the "C" currency format string is changed to the "N" number format string and 2 represents how many decimals to include.

The last thing was to include the Conversion functionality, and having started coding on some fairly elaborate solutions, I thought, "this is similar to formatting". I started to appreciate the diverse mechanisms the Format functions gave me, so using the IFormattable design, I designed the IMoneyConversionProvider (emulating the IFormatProvider) and IMoneyConvertible (emulating the IFormattable) interfaces. And this gave me the ultimate in flexibility, even allowing other developers to create their own conversion engines.

I have provided a separate project which uses the Provider interface and a web service to get the exchange rates which are then used to convert the Money instance to another region via the ToRegion function in the Money structure. (Money implements the IMoneyConvertible interface and the project includes a class which implements the IMoneyConversionProvider.)

MoneyWordFormatter.vb

This class was initially created to test the IFormatProvider functionality and therefore does not use the money culture to output the value in the correct language. It only supports Australian formatting (and similarly formatted text).

The class needed to support both the IFormatProvider and ICustomFormatter interfaces. The FormatProvider exposes the GetFormat function which should return the Formatter for the formatType that is passed in. In this case it just returns an instance of itself as it is the Formatter as well.

I won't go into detail as to how the value is changed into words, you are free to view the code at your leisure.

Testing / Code coverage

Just a quick mention of the Testing support in VS 2005, I won't go into detail as I could easily make it to a full article in it's own right, but it's excellent being able to write or generate the tests in the same application as I am writing the source. I was using NUnit prior to this release of Visual Studio but I'm finding that it has started to take a back seat purely because of the integration.

As I mentioned at the beginning, I started with the diagram, from this stub class I create the tests. Everything failed when I ran it, but that was expected, I still hadn't written the code. As I completed each method, I would run the tests and see how I went. I found that the test stubs created were nearly enough to fully test the final solution (I got more than 82% coverage), however, more were required for exception testing and usage scenarios, to cover the rest. Yes I mentioned coverage, because that is also included (even though it needs to be turned on).

This instantly told me how well the project was tracking and where more effort was required, oh no, I just realized, I won't have any more excuses for my project manager when I run over budget :(

I have included a test project in this demo so that you can see how this is implemented. And here is where I got most of the information for testing.

The demo project

The demo project was built to allow CodeProject users to use all the features of the Money datatype. Here's a quick help guide:

Enter values into Value 1 and hit enter to add two items into the first ListBox for the current region. The arithmetic buttons will work now, displaying the answer in the result textboxes, the first in numeric format for the region, the other in words.

Now enter a few more values to populate the second ListBox, now sum will work, again showing the answers in the two result textboxes.

Hit clear, enter a region name (i.e. au = Australia, dk = Denmark) and a value, then add a different region for the second value and hit convert. This will display Money 1 value converted to the second currency.

The question I still have ????

Does anyone know how I can make my Money structure act more like an intrinsic type? Example:

Dim s asString = "A new string"' Intrinsic Type

Dim m as Money = 2.34 AUD ' Money Type

instead of:

Dim m asNew Money(2.34, New RegionInfo("AUD"))

Feedback

I'd really appreciate any feedback anyone has regarding performance or lack thereof, improvements to this article as it's my first...

History

None... yet.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Marked as Serializable so that if you include the type in your class as a public variable, which happens to be used by a WebService, it will not block the serialization functionality.

All instrinsic data types are and should be serializable, and that was my aim for this type too.

Class vs Structure, well that was personal preference, it could have been a class, but then it would be a "reference" type not a "value" type, and again this goes back to the, "I want to create a new data type" requirement.

The only issue I was not able to solve (and still haven't) is to make it more like a Double or Decimal like stated at the bottom of the article so that we can just write Dim myMoney As Money = 2.45AU

Otherwise you may be able to work around that by having some pre-build sequence that replaces the text of the 2.45AU or 5.50USD to the whole New Money(2.45, New RegionInfo("AUD")). Of course then you would want to build in IDE support so that it does not show an error. So I'm back at my original solution, build your own compiler.

I do like some of the things you did here, and wonder why its so hard to find more things like this. Did you ever take it further? Has anyone seen a c# implementation?

I have done something similar in c#, but struggling with some issues. One thing I have not struggled with are static Factory methods to let me instantiate something like Money m = Money.US(100), so let me know if that is still of interest. Will try to look at your code this w/e!

Glad you like it... I work in a mainly VB.Net software house so I haven't worried about a C# implementation.

I have been using it in a POS system at an airport and works fine. However, I still haven't implemented the string conversion of something like US10.12 as this hasn't been high on the list of requirements.