Search code examples
c#formattingnumber-formatting

C# custom Format Provider for any INumber value?


Really new to this, but trying to implement a CustomFormater class ICustomFormatter The idea is pretty simple but I cannot get it to work without adding hundred of if statements.

Format "K" should output the value divided by 1024, but whatever that value type is, it could be a int, ulong, decimal and so on, any value that implements INumeric

public class myFormatter : IFormatProvider, ICustomFormatter {
  public object? GetFormat(Type? formatType) => formatType == typeof(ICustomFormatter) ? this : null;
  public string Format(string? format, object? arg, IFormatProvider? formatProvider) {
    formatProvider ??= this;
    if (!Equals(formatProvider)) return string.Empty;
    if (arg == null) return string.Empty;
    string resultString = arg.ToString() ?? "";

    if (format.StartsWith('K')) {
      resultString = string.Format("{0:#,0.0} {1}", (double)arg / 1024, format);
    }

    return resultString;
  }
}

But as you could guess, it throws InvalidCastException when arg cannot be converted to double. How do I make it generic? for any given value it would always be divisible by 1024 right?

    var fp = new myFormatter();
    Debug.WriteLine($"{fp.Format("K", 1024d, fp)}"); // Works fine
    Debug.WriteLine($"{fp.Format("K", 1024, fp)}");  // Fails

PS: And yes, I know there are several diff types to convert a number to K and classes/helpers for it, but I just need this simple one and I will implement other formats on my formatter as well.


Solution

  • You can't "cast" an object to double unless the underlying value is actually a double. The same syntax is used for conversion if the variable type is implicitly convertible, but there is no implicit conversion from object to double

    Convert.ToDouble can convert at runtime, though:

    if (format.StartsWith('K')) {
      resultString = string.Format("{0:#,0.0} {1}", Convert.ToDouble(arg) / 1024d, format);
    }
    

    Now the conversion will happen at runtime, and will fail if the value cannot be converted (e.g. a string value that does not represent a valid number). You'll have to decide how to deal with those cases.

    Note that I changed the denominator to a double value also to make the math more straightforward.