![]() |
Languages »
VB.NET »
General
Intermediate
Money DataTypeBy chimeric69An article on creating a Money datatype with localized formatting and plugin conversion support. |
VB.NET 2.0, WinXPVS2005, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||

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.
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?".
The code provided here will be broken into four main sections:
Money structure, and a few support classes.
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:

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.
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:
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
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.
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 was built to allow CodeProject users to use all the features of the Money datatype. Here's a quick help guide:
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.
ListBox, now sum will work, again showing the answers in the two result textboxes.
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 TypeDim m as Money = 2.34 AUD ' Money Type
instead of:
Dim m as New Money(2.34, New RegionInfo("AUD"))
I'd really appreciate any feedback anyone has regarding performance or lack thereof, improvements to this article as it's my first...
None... yet.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 4 Sep 2005 Editor: Rinish Biju |
Copyright 2005 by chimeric69 Everything else Copyright © CodeProject, 1999-2009 Web20 | Advertise on the Code Project |