Answered by:
Method Overload (string and numeric)

Question
-
I've gotten tired of writing code to format a value as currency, so I thought I'd write a method to handle it. And since the values coming from the database I could be a string or a numeric (usually decimal, but stored as money), I thought I'd write two (overloaded) methods as follows:
public static string FormatAsCurrency(string currencyStringValue) if (currencyStringValue == null ) { return ""; } decimal decimalValue; if (decimal.TryParse(currencyStringValue, out decimalValue) == false ) { return currencyStringValue; } return string.Format("{0:C}", decimalValue); } public static string FormatAsCurrency(decimal currencyNumericValue) { return string.Format("{0:C}", currencyNumericValue); }
To test my methods, I wrote the following:
string testString; testString = FormatAsCurrency("1234.56"); testString = FormatAsCurrency("1234.56789"); testString = FormatAsCurrency(""); testString = FormatAsCurrency(" "); testString = FormatAsCurrency("wassup"); testString = FormatAsCurrency(null); testString = FormatAsCurrency(1234); testString = FormatAsCurrency(1234.56);
Visual Studio is OK with all but the last one. On the last one, it reports:
The best overloaded match method match for FormatAsCurrency(string) has some invalid arguments.
If I add a third overloaded method (not shown) to handling double, VS reports it can't decide between the double and decimal versions (something about being ambiguous).
I'm sure there is a totally valid, totally Microsoft explanation. Could someone tell me what that might be?!?
And, irrespective of the answer, if I want to handle numeric, am I going to need to implement a method for each type (long, float, double, and decimal)?
Richard
P.S. - Since I'm asking questions, if I have to make separate methods for each and every numeric type, could anyone tell me whether it'd be better to consolidate all of the formatting code into the decimal version, and have each of the other overloaded methods simply call that one, ala:
public static string FormatAsCurrency(double currencyDoubleValue) { return FormatAsCurrency((decimal)currencyDoubleValue) }
I suspect that doing it separately will be more efficient. On the other hand, you have to update multiple methods if you make any changes. The pushback to the latter is that these will naturally be co-located, and except for the string version, the code is pretty short (one-liners), so making sure that all are modified ain't exactly all that difficult ...
Answers
-
Something similar to this may work for you for all cases....am just using a modified version of your code:
public static string FormatAsCurrency(object currencyValue) { if (currencyValue == null) { return ""; } decimal decimalValue; if (decimal.TryParse(currencyValue.ToString(), out decimalValue) == false) { return currencyValue.ToString(); } return string.Format("{0:C}", decimalValue); }
It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.
- Marked as answer by Caillen Monday, June 29, 2015 3:27 PM
-
You could create a generic method to avoid having write separate methods. It wont compile with a null argument unless you specify a type as it can't be inferred but everything else should work.
public static string FormatAsCurrency<T>(T currencyValue) { if (currencyValue == null) { return ""; } decimal decimalValue; string currencyString = currencyValue.ToString(); if (decimal.TryParse(currencyString, out decimalValue) == false) { return "Not a currency"; } return string.Format("{0:C}", currencyString); }
string testString; testString = FormatAsCurrency("1234.56"); testString = FormatAsCurrency("1234.56789"); testString = FormatAsCurrency(""); testString = FormatAsCurrency(" "); testString = FormatAsCurrency("wassup"); testString = FormatAsCurrency<string>(null); testString = FormatAsCurrency(1234); testString = FormatAsCurrency(1234.56d);
- Proposed as answer by BonnieBMVP Saturday, June 20, 2015 4:02 PM
- Marked as answer by Caillen Monday, June 29, 2015 3:27 PM
-
The type String should be avoided. You need it for output towards and input from the user. But do not store in it (unless unavoidable). Do not process in it (unless unavoidable). The only type worse for storage and processing is binary.
All stuff that inherits from Object (including the value type base struct) has the ToString() method. ToString() has often overloads that allow you to define exact formating.
Parse and TryParse also have proper overrides that take culture and formatting.You need to provide the Formating exactly two times: When getting input from user. When putting output to the user. And it should be easy enough to just put the format into a global constant.
Personally I am not a huge fan of storing or processing currency values in floats. Thier limits of precision can be confusing.
It is so much easier to just store and process money in cents (1$ = 100 cent) and thus use an integer. If you need higher precision (legal reasons) just use deci-cent (4th place behind decimal) as storage form. If you need really large values, there is always a bigger unsigned int. And a BigInt structure.
The only thing that really changes are the parsing and ToString formatting. Wich I consider a minor detail of the User Interface, not a part of the business logic.
But that is only my 2 Cents or 0,02$- Marked as answer by Caillen Monday, June 29, 2015 3:27 PM
All replies
-
If you want a numeric real literal to be treated as decimal, use the suffix m or M, for example:
decimal myMoney = 300.5m;
Without the suffix m, the number is treated as a double and generates a compiler error.
It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.
-
The ambiguity comes with this line:
testString = FormatAsCurrency(1234);
Because the number could be considered a decimal or it could be a double. If you cast 1234 to one or the other, then the ambiguity goes away...something like:
testString = FormatAsCurrency((double)1234);
It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.
-
You don't necessarily have to create an overload for each individual type unless you plan on reaching the limits of each type. As long as one of the types will meet all of your range criteria, you could allow them to be coerced into the method type or you could cast it to an appropriate type as you are passing it to the function.
So, for instance, if you were using double for the function:
public static string FormatAsCurrency(double currencyDoubleValue) { throw new NotImplementedException(); }
You could cast the input decimal value into a double:
testString = FormatAsCurrency((double)1234.56m);
Of course this only works if you know your data is not going to push the extremes and only you know which type is the most appropriate.
It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.
-
Something similar to this may work for you for all cases....am just using a modified version of your code:
public static string FormatAsCurrency(object currencyValue) { if (currencyValue == null) { return ""; } decimal decimalValue; if (decimal.TryParse(currencyValue.ToString(), out decimalValue) == false) { return currencyValue.ToString(); } return string.Format("{0:C}", decimalValue); }
It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.
- Marked as answer by Caillen Monday, June 29, 2015 3:27 PM
-
You could create a generic method to avoid having write separate methods. It wont compile with a null argument unless you specify a type as it can't be inferred but everything else should work.
public static string FormatAsCurrency<T>(T currencyValue) { if (currencyValue == null) { return ""; } decimal decimalValue; string currencyString = currencyValue.ToString(); if (decimal.TryParse(currencyString, out decimalValue) == false) { return "Not a currency"; } return string.Format("{0:C}", currencyString); }
string testString; testString = FormatAsCurrency("1234.56"); testString = FormatAsCurrency("1234.56789"); testString = FormatAsCurrency(""); testString = FormatAsCurrency(" "); testString = FormatAsCurrency("wassup"); testString = FormatAsCurrency<string>(null); testString = FormatAsCurrency(1234); testString = FormatAsCurrency(1234.56d);
- Proposed as answer by BonnieBMVP Saturday, June 20, 2015 4:02 PM
- Marked as answer by Caillen Monday, June 29, 2015 3:27 PM
-
The type String should be avoided. You need it for output towards and input from the user. But do not store in it (unless unavoidable). Do not process in it (unless unavoidable). The only type worse for storage and processing is binary.
All stuff that inherits from Object (including the value type base struct) has the ToString() method. ToString() has often overloads that allow you to define exact formating.
Parse and TryParse also have proper overrides that take culture and formatting.You need to provide the Formating exactly two times: When getting input from user. When putting output to the user. And it should be easy enough to just put the format into a global constant.
Personally I am not a huge fan of storing or processing currency values in floats. Thier limits of precision can be confusing.
It is so much easier to just store and process money in cents (1$ = 100 cent) and thus use an integer. If you need higher precision (legal reasons) just use deci-cent (4th place behind decimal) as storage form. If you need really large values, there is always a bigger unsigned int. And a BigInt structure.
The only thing that really changes are the parsing and ToString formatting. Wich I consider a minor detail of the User Interface, not a part of the business logic.
But that is only my 2 Cents or 0,02$- Marked as answer by Caillen Monday, June 29, 2015 3:27 PM