3
\$\begingroup\$

Am making a pure .net library with helper functions (GitHub).

However I wanted to have a thousand separator for all number types and here is what I am currently doing

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Avo
{
    public static class Extension_Number
    {
        #region ThousandSeparator
        public static string ToThousandSeparator(this decimal value, int numberOfDecimalPlaces)
        {
            if (numberOfDecimalPlaces < 0) numberOfDecimalPlaces = 0;
            return string.Format("{0:N" + numberOfDecimalPlaces + "}", value).ToString();
        }
        public static string ToThousandSeparator(this int value, int numberOfDecimalPlaces)
        {
            if (numberOfDecimalPlaces < 0) numberOfDecimalPlaces = 0;
            return string.Format("{0:N" + numberOfDecimalPlaces + "}", value).ToString();
        }
        public static string ToThousandSeparator(this double value, int numberOfDecimalPlaces)
        {
            if (numberOfDecimalPlaces < 0) numberOfDecimalPlaces = 0;
            return string.Format("{0:N" + numberOfDecimalPlaces + "}", value).ToString();
        }
        public static string ToThousandSeparator(this long value, int numberOfDecimalPlaces)
        {
            if (numberOfDecimalPlaces < 0) numberOfDecimalPlaces = 0;
            return string.Format("{0:N" + numberOfDecimalPlaces + "}", value).ToString();
        }
        #endregion
    }
}

As you can see above ,

l have to repeat the same for int , decimal , long

l can achieve the results l want but is it best

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Avo
{
    class Program
    {
        static void Main(string[] args)
        {
            decimal tes = 294944.8484827M;
            int iint = 34;
            Console.WriteLine(tes.ToThousandSeparator(3));
            Console.WriteLine(iint.ToThousandSeparator(2)); 
            Console.ReadKey();
        }
    }
}

but is there an eloquent way of doing it , but allowing number types only.

\$\endgroup\$
4
\$\begingroup\$

There is no common base type for numeric types, unfortunately.

You could still reduce the repetitiveness of your code though, by encapsulating the common structure in a single function:

    private static string AddThousandsSeparator(Object numeric, int numberOfDecimalPlaces)
    {
        // note this would crash when passed a non-numeric object.
        // that's why it's private, and it's the class's responsibility
        // to limit the entry points to this function to numeric types only
        return String.Format("{0:N" + Math.Max(0, numberOfDecimalPlaces) + "}", numeric);
    }

    public static string WithThousandsSeparator(this decimal value, int numberOfDecimalPlaces)
    {
        return AddThousandsSeparator(value, numberOfDecimalPlaces);
    }

    public static string WithThousandsSeparator(this int value, int numberOfDecimalPlaces)
    {
        return AddThousandsSeparator(value, numberOfDecimalPlaces);
    }

    public static string WithThousandsSeparator(this double value, int numberOfDecimalPlaces)
    {
        return AddThousandsSeparator(value, numberOfDecimalPlaces);
    }

    public static string WithThousandsSeparator(this long value, int numberOfDecimalPlaces)
    {
        return AddThousandsSeparator(value, numberOfDecimalPlaces);
    }

A few side remarks:

  • There is no point in calling ToString() on the result of String.Format. String.Format already returns a string.

  • Math.Max is a more concise alternative for the conditional reassignment. (For what it's worth, Kotlin provides readable extension functions as syntax sugar for this sort of thing, eg. someNumber.coerceAtLeast(0) - trivially easy to reimplement them in C# for readability).

  • I also corrected the naming slightly. It's "thousands" separator (plural), plus I think With... makes it clearer what the function does. The ToSomething phrase conventionally means a value is getting converted to Something, as if you were converting the number to the thousands separator itself - obviously nonsensical - rather than just adding it to the formatting.

\$\endgroup\$
  • 1
    \$\begingroup\$ making AddThousandsSeparator private is nice and the other types public , thus make change to a single function applies to all , thanks for that \$\endgroup\$ – Billy Watsy Mar 7 at 11:31
  • \$\begingroup\$ Sure thing, happy to be of help \$\endgroup\$ – Konrad Morawski Mar 7 at 11:36
  • 2
    \$\begingroup\$ You can slightly optimize it by making the Object parameter an IFormattable. \$\endgroup\$ – t3chb0t Mar 7 at 11:46
  • \$\begingroup\$ That's a valid remark (well, it's been a while since I coded in C# day to day), although instinctively I feel that the more specific the parameter type, the more of a false sense of type security it projects. It's a tradeoff of sorts. \$\endgroup\$ – Konrad Morawski Mar 7 at 12:35
4
\$\begingroup\$

I would definitely go with t3chb0t and replace Object with IFormattable in Konrads answer. Further you could provide default values to numberOfDecimalPlaces of your own choice and maybe the possibility to provide a FormatProvider which defaults to CultureInfo.CurrentCulture:

  public static class Extension_Number
  {
    private static string Format(this IFormattable value, int decimalPlaces, IFormatProvider formatProvider = null) => value.ToString($"N{Math.Max(0, decimalPlaces)}", formatProvider ?? CultureInfo.CurrentCulture);

    public static string ToThousandSeparator(this decimal value, int numberOfDecimalPlaces = 2, IFormatProvider formatProvider = null)
    {
      return value.Format(numberOfDecimalPlaces, formatProvider);
    }
    public static string ToThousandSeparator(this int value, int numberOfDecimalPlaces = 0, IFormatProvider formatProvider = null)
    {
      return value.Format(numberOfDecimalPlaces, formatProvider);
    }
    public static string ToThousandSeparator(this double value, int numberOfDecimalPlaces = 6, IFormatProvider formatProvider = null)
    {
      return value.Format(numberOfDecimalPlaces, formatProvider);
    }
    public static string ToThousandSeparator(this long value, int numberOfDecimalPlaces = 0, IFormatProvider formatProvider = null)
    {
      return value.Format(numberOfDecimalPlaces, formatProvider);
    }
  }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.