Neat Tricks for Effortlessly Formatting Currency in C#

Neat Tricks for Effortlessly Formatting Currency in C#

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$
}

Related Articles

Development, Resources

Most Complete NUnit Unit Testing Framework Cheat Sheet

An essential part of every UI test framework is the usage of a unit testing framework. One of the most popular ones in the .NET world is NUnit. However, you can

Most Complete NUnit Unit Testing Framework Cheat Sheet

Development, Resources

Most Complete MSTest Unit Testing Framework Cheat Sheet

An essential part of every UI test framework is the usage of a unit testing framework. One of the most popular ones in the .NET world is MSTest. However, you ca

Most Complete MSTest Unit Testing Framework Cheat Sheet

Development

Optimize C# Reflection Up to 10 Times by Using Delegates

Developers love reflection because it can save them numerous hours of boilerplate code. But developers also know reflection is slow and it should be used with c

Optimize C# Reflection Up to 10 Times by Using Delegates

Development

Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator

After my article Top 15 Underutilized Features of .NET provoked an interesting discussion. I was curious to learn which method is faster- ?? (null coalescing op

Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator

Development

Assert DateTime the Right Way MSTest NUnit C# Code

NUnit has added built-in support for this using the keyword Within.

Assert DateTime the Right Way MSTest NUnit C# Code

Development

Get Property Names Using Lambda Expressions in C#

In this article, I am going to present to you how to get property and method names from lambda expressions. You can pass properties and methods as methods' para

Get Property Names Using Lambda Expressions in C#
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.