In the article, I am going to present to you a few ways how to create a handy class that effortlessly formatting currency in C#. Initially, I came up with the idea because I needed to write several System/UI tests that needed to verify price amounts on the site that our team was developing. However, the currency formats on the different web forms were different, so I decided to write a utility for the job. I think it is worth sharing because it is taking advantage of several underutilized features of the C# language like Flags attribute, dynamic, generic and extension methods.
Formatting Currency Needs
Neat Tricks for Effortlessly Formatting Currency in C#
Combine Formatting Options Through Flags Attribute
As pointed previously we have a few possible scenarios for currency formatting. To be able to combine them I have created a special enum decorated with Flags attribute- DigitsFormattingSettings.
public enum DigitsFormattingSettings
{
None = 0,
NoComma = 1,
PrefixDollar = 2,
PrefixMinus = 4,
SufixDollar = 8
}
Flags enumerations are used for masking bit fields and doing bitwise comparisons. They are the correct design to use when multiple enumeration values can be specified at the same time.
public enum DigitsFormattingSettingsBitShifting
{
None = 0,
NoComma = 1 << 0,
PrefixDollar = 1 << 1,
PrefixMinus = 1 << 2,
SufixDollar = 1 << 3
}
Note: You cannot use the None enumerated constant in a bitwise AND operation to test for a flag because the result is always zero. However, you can perform a logical, not a bitwise, comparison between the numeric value and the None enumerated constant to determine whether any bits in the numeric value are set.
Extension ToString Methods for Currency Formatting
My main idea was to create a class holding multiple double and decimal extensions methods that provide the desired formatting capabilities. Initially, the primary method that handled the effortless formatting required a dynamic parameter (the number that was going to be formatted). Then the method was called internally from all public extension methods.
public static class CurrencyDelimiterFormatter
{
private static readonly CultureInfo usCultureInfo = CultureInfo.CreateSpecificCulture("en-US");
public static string ToStringUsDigitsFormatting(
this double number,
DigitsFormattingSettings digitsFormattingSettings = DigitsFormattingSettings.None,
int precesion = 2)
{
string result = ToStringUsDigitsFormattingInternal(number, digitsFormattingSettings, precesion);
return result;
}
public static string ToStringUsDigitsFormatting(
this decimal number,
DigitsFormattingSettings digitsFormattingSettings = DigitsFormattingSettings.None,
int precesion = 2)
{
string result = ToStringUsDigitsFormattingInternal(number, digitsFormattingSettings, precesion);
return result;
}
private static string ToStringUsDigitsFormattingInternal(
dynamic number,
DigitsFormattingSettings digitsFormattingSettings = DigitsFormattingSettings.None,
int precesion = 2)
{
string formattedDigits = string.Empty;
string currentNoComaFormatSpecifier = string.Concat("#.", new string('0', precesion));
string currentComaFormatSpecifier = string.Concat("##,#.", new string('0', precesion));
formattedDigits =
digitsFormattingSettings.HasFlag(DigitsFormattingSettings.NoComma) ? number.ToString(currentNoComaFormatSpecifier, usCultureInfo) :
number.ToString(currentComaFormatSpecifier, usCultureInfo);
if (digitsFormattingSettings.HasFlag(DigitsFormattingSettings.PrefixDollar))
{
formattedDigits = string.Concat("$", formattedDigits);
}
if (digitsFormattingSettings.HasFlag(DigitsFormattingSettings.PrefixMinus))
{
formattedDigits = string.Concat("-", formattedDigits);
}
if (digitsFormattingSettings.HasFlag(DigitsFormattingSettings.SufixDollar))
{
formattedDigits = string.Concat(formattedDigits, "$");
}
return formattedDigits;
}
}
I didn’t want to duplicate the formatting logic twice for double and decimal because of that I came up with this solution. But as you probably know variables of type dynamic are compiled into variables of type object. As a result of the boxing and unboxing between value types and object, the formatting was going to be a little bit slower compared to other alternatives.
Improved Generic Extension ToString Methods for Currency Formatting
As pointed above there are better alternatives of my initial implementation. One of them is the usage of generic methods.
private static string ToStringUsDigitsFormattingInternal<T>(
T number,
DigitsFormattingSettings digitsFormattingSettings = DigitsFormattingSettings.None,
int precesion = 2)
where T : struct,
IComparable,
IComparable<T>,
IConvertible,
IEquatable<T>,
IFormattable
{
string formattedDigits = string.Empty;
string currentNoComaFormatSpecifier = string.Concat("#.", new string('0', precesion));
string currentComaFormatSpecifier = string.Concat("##,#.", new string('0', precesion));
formattedDigits =
digitsFormattingSettings.HasFlag(DigitsFormattingSettings.NoComma) ? number.ToString(currentNoComaFormatSpecifier, usCultureInfo) :
number.ToString(currentComaFormatSpecifier, usCultureInfo);
if (digitsFormattingSettings.HasFlag(DigitsFormattingSettings.PrefixDollar))
{
formattedDigits = string.Concat("$", formattedDigits);
}
if (digitsFormattingSettings.HasFlag(DigitsFormattingSettings.PrefixMinus))
{
formattedDigits = string.Concat("-", formattedDigits);
}
if (digitsFormattingSettings.HasFlag(DigitsFormattingSettings.SufixDollar))
{
formattedDigits = string.Concat(formattedDigits, "$");
}
return formattedDigits;
}
string currentNoComaFormatSpecifier = string.Concat("#.", new string('0', precesion));
string currentComaFormatSpecifier = string.Concat("##,#.", new string('0', precesion));
The zeroes for the specified precision are generated through the special string constructor that creates a new string from a specified character.
With the release of .NET 4.0, developers found a new method to handle the “dirty work” of checking to see if an enum has a flag. Every enum instance now has this method. You just pass the flag value you want to check for. I am using it to check if an additional formatting is needed as adding a dollar sign as a prefix/suffix, or the minus sign.
Formatting Currency in C# Example Usage
Usually, I set the namespace for my extension methods classes to be equal to the most basic one. In this example - CSharp.Series.Tests. This ease the usage of the extension methods around the project because no additional using statements are required.
public static unsafe void Main(string[] args)
{
Console.WriteLine(1220.5.ToStringUsDigitsFormatting(DigitsFormattingSettings.PrefixDollar));
// Result- $1,220.50
Console.WriteLine(1220.5.ToStringUsDigitsFormatting(DigitsFormattingSettings.SufixDollar | DigitsFormattingSettings.NoComma));
// Result- 1220.50$
Console.WriteLine(1220.53645.ToStringUsDigitsFormatting(DigitsFormattingSettings.SufixDollar | DigitsFormattingSettings.NoComma | DigitsFormattingSettings.PrefixMinus, 4));
// Result- -1220.5365$
} 