1

I am trying to write a C# generic method that accepts nullable decimal and double values and converts them to a string representation.

I am getting the error "No overload for method 'ToString' takes 1 arguments" although I am accessing .Value of the nullable parameter.

Here is my code. What am I doing wrong?

public static string ToThousandSeparated<T>(T? value, string naString = "") where T : struct
{
    if (value.HasValue)
    {
        T val = value.Value;
        return val.ToString("N0");
    }

    return naString;
}
lukin155
  • 63
  • 8
  • 2
    In your code, `T` is a `ValueType` and it has only one `ToString` overload which takes no parameter. https://learn.microsoft.com/en-us/dotnet/api/system.valuetype.tostring?view=net-5.0 – Soner Gönül May 19 '21 at 13:15
  • 1
    The problem here is that `T` is not necessarily a `struct` that has a `ToString(string format)` defined like `int` or `dobule` does. Maybe pass in a `Func` so you can specify how to format the value. Or if you just need this for `decimal?` and `double?` then write two separate methods for each. – juharr May 19 '21 at 13:15
  • Just write two methods. making this method generic doesn't solve any problems if doesn't add any. – Bizhan May 19 '21 at 13:20

2 Answers2

7

object only defines the method string ToString() (with no parameters). Objects like Int32 define their own string ToString(string) method.

However, there's a useful interface called IFormattable, which provides a string ToString(string, IFormatProvider) method. So you can either constrain yourself to all T which implement IFormattable:

public static string ToThousandSeparated<T>(T? value, string naString = "")
    where T : struct, IFormattable
{
    if (value.HasValue)
    {
        T val = value.Value;
        return val.ToString("N0", null);
    }

    return naString;
}

or accept anything, but test whether it implements IFormattable at runtime:

public static string ToThousandSeparated<T>(T? value, string naString = "")
    where T : struct
{
    if (value.HasValue)
    {
        T val = value.Value;
        return val is IFormattable formattable ? formattable.ToString("N0", null) : val.ToString();
    }

    return naString;
}
canton7
  • 37,633
  • 3
  • 64
  • 77
  • here is not called 'object.ToString' but 'ValueType.ToString` – Bizhan May 19 '21 at 13:18
  • The problem with the second choice (the one with testing) is that if it is not `IFormattable` then the method does not do what was intented to and caller has no way to know the call did not separate the thousands. IMHO first option is safer in this use case. Anyway, I am upvoting this answer – Cleptus May 19 '21 at 14:27
  • Indeed, it should be obvious that the first one should be used if you want to force people to only use it with `IFormattable` types, and the second should be used if you want to make a best-effort attempt to format the parameter – canton7 May 19 '21 at 14:29
0

When you use .ToString() there are two places it will look to see for overload resolution.

It will first check to see if the object you're calling has overloaded the ToString method.

If it hasn't it will by default call Object.ToString.

In your example you want to use a nullable decimal, which DOES override ToString with several versions that accept parameters.

However, since you're calling it from a generic method, the compiler has no way of knowing the object you're calling ToString() on is a decimal all it knows is that T is a struct.

Consider checking to see if the object is an IFormattable using the is operator

DekuDesu
  • 2,224
  • 1
  • 5
  • 19