Click here to Skip to main content
Click here to Skip to main content

Money DataType

, 4 Sep 2005
Rate this:
Please Sign up or sign in to vote.
An article on creating a Money datatype with localized formatting and plugin conversion support.

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?".

So, my main objectives became

  • Store the currency type with the value (i.e. AUD, US, DKK).
  • Store formatting details (i.e. decimal grouping, currency symbols, etc.).
  • Conversion providers, I didn't want to hard code this as it is a datatype and not a solution.
  • Development safety features (i.e. prevent arithmetic on different currency types).
  • For fun, provide "Custom" formatter support for those special requirements (i.e. Numbers to Words for checking output).

Using the code

The code provided here will be broken into four main sections:

  1. Money - this contains the new Money structure, and a few support classes.
  2. Money.Convertor - contains an example Money converter using a free Web service from WebServiceX which provides exchange rates.
  3. Money.Demo - a demo form written specifically for CodeProject readers to demo the features of the Money datatype.
  4. MoneyTests - a test project (new to VS2005) that allows integrated testing and code coverage.

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?).

Dim regionName As String = region.Name.ToLower
If regionName.IndexOf("-"c) = -1 Then regionName = "-" & regionName
For Each c As CultureInfo In CultureInfo.GetCultures(_
                                CultureTypes.SpecificCultures)
    If c.Name.ToLower.IndexOf(regionName) > -1 Then
        Return c
    End If
Next

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.

''' <summary>
'''
''' </summary>
''' <param name="value"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overloads Function Equals(ByVal value As _
                                 Money) As Boolean

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.

Public Overloads Function ToString(ByVal formatProvider As System.IFormatProvider) As String - 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.

Return String.Format(formatProvider, "{0}", _value)

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

Public Overloads Function ToString(ByVal format As String, ByVal formatProvider As System.IFormatProvider) As String Implements System.IFormattable.ToString - This is a combination of both the other two, the format string and the FormatProvider are supplied.

Dim money1 as New Hybrid.Money(1234.4567D)
Debug.WriteLine(money1.ToString("N2",NumberFormatInfo))

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.)

Public Function ToRegion(ByVal region As System.Globalization.RegionInfo, _
                       ByVal convertProvider As IMoneyConversionProvider) _
                       As Money Implements IMoneyConvertible.ToRegion

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.

Public Function GetFormat(ByVal formatType As System.Type) As _
                 Object Implements System.IFormatProvider.GetFormat

CustomFormatter exposes a Format function which is responsible for the actual output appearance, the words representing the money argument are passed in.

Public Function Format(ByVal formatString As String, _
                      ByVal arg As Object, ByVal formatProvider As _
                      System.IFormatProvider) As String Implements _
                      System.ICustomFormatter.Format

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:

  1. 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.
  2. Now enter a few more values to populate the second ListBox, now sum will work, again showing the answers in the two result textboxes.
  3. 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 as String = "A new string" ' Intrinsic Type
Dim m as Money = 2.34 AUD        ' Money Type

instead of:

Dim m as New 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.

A list of licenses authors might use can be found here

Share

About the Author

chimeric69
Web Developer
Australia Australia
No Biography provided

Comments and Discussions

 
GeneralIComparable<T> support for LINQ OrderBy Pinmembermeaningoflights14-Jun-10 17:31 
Hi,
 
The Money Datatype needs to implement IComparable if you wish to use LINQ to query money collections:
 
Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
        Dim otherMoney As Money = CType(obj, Money)
 
        If Not IsNothing(otherMoney) Then
            Return Me.Value.CompareTo(otherMoney.Value)
        Else
            Throw New ArgumentException("Object is not a Money object")
        End If
    End Function
 
eg:
Dim orderedThresholds = thresholds.OrderBy(Function(tOrder) tOrder.ThresholdMin).ToList()
 
The other change I made was with the Equals method:
Public Overloads Function Equals(ByVal value As Money) As Boolean
        If IsNothing(_region) Then
            _region = Globalization.RegionInfo.CurrentRegion
        End If
 
        If _region.Equals(value.Region) Then
            If _value = value.Value Then

 
thanks for the datatype, much appreciated (5)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 4 Sep 2005
Article Copyright 2005 by chimeric69
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid