57

I want to write a function that format int and decimal differently into string

I have this code:

and I want to rewrite it to generics:

    public static string FormatAsIntWithCommaSeperator(int value)
    {
        if (value == 0 || (value > -1 && value < 1))
            return "0";
        return String.Format("{0:#,###,###}", value);
    }

    public static string FormatAsDecimalWithCommaSeperator(decimal value)
    {
        return String.Format("{0:#,###,###.##}", value);
    }


    public static string FormatWithCommaSeperator<T>(T value) where T : struct
    {
        string formattedString = string.Empty;

        if (typeof(T) == typeof(int))
        {
            if ((int)value == 0 || (value > -1 && value < 1))
            return "0";

            formattedString = String.Format("{0:#,###,###}", value);
        }

        //some code...
    }

    /// <summary>
    /// If the number is an int - returned format is without decimal digits
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string FormatNumberTwoDecimalDigitOrInt(decimal value)
    {
        return (value == (int)value) ? FormatAsIntWithCommaSeperator(Convert.ToInt32(value)) : FormatAsDecimalWithCommaSeperator(value);
    }

How can i use T in the function body?

What syntax should I use?

Elad Benda
  • 35,076
  • 87
  • 265
  • 471
  • 6
    Why not just have two overloads? I'm guessing there is a reason for this but from your example I'd rather have two methods instead of switching on type. – Paulie Waulie Mar 21 '12 at 10:23
  • 1
    Why do you use generics here? Is your method usable for ANY struct, even for one I defined myself? – Daniel Hilgarth Mar 21 '12 at 10:23
  • 1
    Generics are not helpful here, you might as well use *object* as the argument type. Note how the code is the practically the same. – Hans Passant Mar 21 '12 at 10:28
  • Do you simply want the decimal place to be ommited if the value has no fractional part? – Jodrell Mar 21 '12 at 10:43

12 Answers12

77

You can use the TypeCode enum for switch:

switch (Type.GetTypeCode(typeof(T)))
{
    case TypeCode.Int32:
       ...
       break;
    case TypeCode.Decimal:
       ...
       break;
}

Since C# 7.0 you can use pattern matching:

switch (obj)
{
    case int i:
       ...
       break;
    case decimal d:
       ...
       break;
    case UserDefinedType u:
       ...
       break;
}

Beginning with C# 8.0 you can use switch expressions:

string result = obj switch {
    int i => $"Integer {i}",
    decimal d => $"Decimal {d}",
    UserDefinedType u => "User defined {u}",
    _ => "unexpected type"
};
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Nikola Markovinović
  • 18,963
  • 5
  • 46
  • 51
49

Another way to do switch on generic is:

switch (typeof(T))
{
    case Type intType when intType == typeof(int):
        ...
    case Type decimalType when decimalType == typeof(decimal):
        ...
    default:
        ...
}

Note that when as a case guard in switch expressions was introduced in C# 7.0/Visual Studio 2017.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
flam3
  • 1,897
  • 2
  • 19
  • 26
43

In modern C#:

public static string FormatWithCommaSeperator<T>(T value) where T : struct
{
    switch (value)
    {
        case int i:
            return $"integer {i}";
        case double d:
            return $"double {d}";
    }
}
Tamir Daniely
  • 1,659
  • 1
  • 20
  • 24
  • 51
    what happens if I only got the T but not a value of T ? say public T Get(string name){ } – Avlin Jul 13 '18 at 08:02
  • 5
    You could use use `default(T)`. But I'm not sure if that's faster then just having a dictionary with key type. If what you want is a keyed collection that returns different types, just keep the value as the common base type (or Object), and either do a direct cast in get that throws, or use `as` which returns null. that's perfect behaviour, doing the type check won't save you any code, and only complicate error handling. – Tamir Daniely Jul 14 '18 at 20:31
  • 5
    Correcting myself, The default(T) solution won't work for ref types, because the default is null. So you'll have to just test the type with, or probably just find a better design. – Tamir Daniely Jul 25 '18 at 21:22
15

I had a similar question, but with custom classes rather than built-in data types. Here's how I went about it:

switch (typeof(T).Name)
{
    case nameof(Int32):
        break;
    case nameof(Decimal):
        break;
}

I modified it to use the types you are using (i.e., int and decimal). I like this approach more than hard coding strings, as a refactor of a class name will not break this code.

With newer versions of C#, you could also do this some of the time:

switch (Activator.CreateInstance(typeof(T)))
{
    case int _:
        break;
    case decimal _:
        break;
}

I say "some of the time" because that would only really work with types that have a default constructor. This approach uses pattern matching and discards. I don't really like it since you need to create an instance of the object (that you then throw away) and because of the default constructor requirement.

Nicholas Westby
  • 1,109
  • 13
  • 32
5

If you have an object, you can use C# 7 pattern matching. But if you don't have an object and want to switch on a generic type T, the best and fastest solution is just a ternary if operator.

public string TernaryIf<T>() =>
    typeof(T) == typeof(int) ? "#,###,###" :
    typeof(T) == typeof(decimal) ? "#,###,###.##" :
    null;

public string TrueSwitch<T>() =>
    true switch
    {
        true when typeof(T) == typeof(int) => "#,###,###",
        true when typeof(T) == typeof(decimal) => "#,###,###.##",
        _ => null,
    };

public string DefaultSwitch<T>() =>
    default(T) switch
    {
        int => "#,###,###",
        decimal => "#,###,###.##",
        _ => null,
    };

public string When_Switch<T>() =>
    typeof(T) switch
    {
        Type _ when typeof(T) == typeof(int) => "#,###,###",
        Type _ when typeof(T) == typeof(decimal) => "#,###,###.##",
        _ => null,
    };

public string TypeCodeSwitch<T>() =>
    Type.GetTypeCode(typeof(T)) switch
    {
        TypeCode.Int32 => "#,###,###",
        TypeCode.Decimal => "#,###,###.##",
        _ => null,
    };

public string WhenSwitch<T>() =>
    typeof(T) switch
    {
        Type intType when intType == typeof(int) => "#,###,###",
        Type decimalType when decimalType == typeof(decimal) => "#,###,###.##",
        _ => null,
    };

public string NameOfSwitch<T>() =>
    typeof(T).Name switch
    {
        nameof(Int32) => "#,###,###",
        nameof(Decimal) => "#,###,###.##",
        _ => null,
    };

public string ActivatorSwitch<T>() =>
    Activator.CreateInstance(typeof(T)) switch
    {
        int => "#,###,###",
        decimal => "#,###,###.##",
        _ => null,
    };

Benchmarks:

Method Time
TernaryIf 0.37 ns
TrueSwitch 0.37 ns
DefaultSwitch 0.48 ns
When_Switch 1.92 ns
TypeCodeSwitch 3.85 ns
WhenSwitch 3.96 ns
NameOfSwitch 7.98 ns
ActivatorSwitch 12.10 ns
palota
  • 465
  • 4
  • 8
  • Thanks palota this saved me from writing some disgusting code which I very much wanted to avoid. – Anil Aug 23 '23 at 06:19
5

Edit: If you only want to handle exactly int and double, just have two overloads:

DoFormat(int value)
{
}

DoFormat(double value)
{
}

If you insist on using generics:

switch (value.GetType().Name)
{
    case "Int32":
        break;
    case "Double":
        break;
    default:
        break;
}

OR

if (value is int)
{
    int iValue = (int)(object)value;
}
else if (value is double)
{
    double dValue = (double)(object)value;
}
else
{
}
SimpleVar
  • 14,044
  • 4
  • 38
  • 60
  • You're right. I updated the post (added object cast). Anyways, I like the answer of Nikola better anyways - its much more readable. – SimpleVar Mar 21 '12 at 10:39
  • `public T myMethod(T value) where T : object` is fine too, and you dont need cast!! – Lucas Nov 21 '16 at 17:13
4

more formatted way to do switch on generic is:

switch (true)
{
    case true when typeof(T) == typeof(int):
        ...
    case true when typeof(T) == typeof(decimal):
        ...
    default:
        ...
}
shtse8
  • 1,092
  • 12
  • 20
4

From C# 8 it's possible to do it this way which also works with Nullable:

switch ((Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T)).Name)
{
    case nameof(Int32):
        ...

    case nameof(Decimal):
        ...

    case nameof(Boolean):  // You can also switch types like 'bool' or Nullable 'bool?'
        ...

    case nameof(String):   // Why not to use 'string'?
        ...

    default:
        ...
}

If you prefer switch expressions you can use:

return (Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T)).Name switch
{
    nameof(Int32) => ...,
    nameof(Decimal) => ...,
    nameof(Boolean) => ...,  // You can also switch types like 'bool' or Nullable 'bool?'
    nameof(String) => ...,   // Why not to use 'string'?
    _ => ...,
};
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
B8ightY
  • 469
  • 5
  • 5
0

In C# 8 you can use (replace "..." with the relevant code):

... type switch
{
    Type _ when type == typeof(int) => ...,
    Type _ when type == typeof(decimal) => ...,
    _ => ... // default case
};

Another elegant option (replace "..." with the relevant code):

... Type.GetTypeCode(type) switch
{
    TypeCode.Int32 => ...,
    TypeCode.Decimal => ...,
    _ => ...
};

For more info: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression

Misha Zaslavsky
  • 8,414
  • 11
  • 70
  • 116
0

Alternatively you could always do:

public static string FormatWithCommaSeparator<T>(T[] items)
{
    var itemArray = items.Select(i => i.ToString());

    return string.Join(", ", itemArray);
}
Adrian Thompson Phillips
  • 6,893
  • 6
  • 38
  • 69
0

You could check the type of the variabele;

    public static string FormatWithCommaSeperator<T>(T value)
    {
        if (value is int)
        {
            // Do your int formatting here
        }
        else if (value is decimal)
        {
            // Do your decimal formatting here
        }
        return "Parameter 'value' is not an integer or decimal"; // Or throw an exception of some kind?
    }
Brian
  • 1,803
  • 1
  • 16
  • 22
0

You could instead of using generics use IConvertible

    public static string FormatWithCommaSeperator(IConvertible value)
    {
            IConvertible convertable = value as IConvertible;
            if(value is int)
            {
                int iValue = convertable.ToInt32(null);
                //Return with format.
            }
            .....
    }
Paulie Waulie
  • 1,690
  • 13
  • 23