'generic type conversion in C#

Is there any way the following code can be improved. I'm aware of the non nullable reference in C# 8.0 and was thinking either that or any other way they code below could be made more robust and better in general. This method is called for converting xml data before insertion in a database Through entity framework core. Any way these tools can be used to improve this code is welcomed.

public object Convert(string value, Type toType)
    {
        try
        {
            if (toType == typeof(short))
            {
                return short.Parse(value);
            }
            if (toType == typeof(short?))
            {
                if (string.IsNullOrEmpty(value))
                {
                    return null;
                }
                return short.Parse(value);
            }
            if (toType == typeof(int))
            {
                return int.Parse(value);
            }
            if (toType == typeof(int?))
            {
                if (string.IsNullOrEmpty(value))
                {
                    return null;
                }
                return int.Parse(value);
            }
            if (toType == typeof(decimal))
            {
                return decimal.Parse(value);
            }
            if (toType == typeof(decimal?))
            {
                if (string.IsNullOrEmpty(value))
                {
                    return null;
                }
                return decimal.Parse(value);
            }
            if (toType == typeof(DateTime))
            {
                return DateTime.Parse(value);
            }
            if (toType == typeof(DateTime?))
            {
                if (string.IsNullOrEmpty(value))
                {
                    return null;
                }
                return DateTime.Parse(value);
            }

            throw new NotSupportedException($"No conversion defined for type:'{toType}'");
        }
        catch (System.FormatException excp)
        {
            throw new ConversionException($"Value:'{value}' could not be converted to:'{toType.Name}'", excp);
        }
    }

Many thanks in advance



Solution 1:[1]

The only thing I can offer is to improve robustness by using .TryParse() for the parsing instead of .Parse()

class Program
{
    static void Main(string[] args)
    {
        var i = Parse<int>("100");
        var x = Parse<double>("3.1417");
        var s = Parse<string>("John");
        var d = Parse<Decimal>("1234.56");
        var f = Parse<DateTime>("4/1/2044");
        var q = Parse<byte>("4A");

        Decimal? p = Parse<decimal>("Not Decimal");
    }

    public static dynamic Parse<T>(string text)
    {
        var toType = typeof(T);
        if (toType == typeof(int))
        {
            if (int.TryParse(text, out int x))
            {
                return x;
            }
        }
        else if (toType == typeof(short))
        {
            if (short.TryParse(text, out short x))
            {
                return x;
            }
        }
        else if (toType == typeof(double))
        {
            if (double.TryParse(text, out double x))
            {
                return x;
            }
        }
        else if (toType == typeof(decimal))
        {
            if (decimal.TryParse(text, out decimal x))
            {
                return x;
            }
        }
        else if (toType == typeof(DateTime))
        {
            if (DateTime.TryParse(text, out DateTime x))
            {
                return x;
            }
        }
        else if (toType == typeof(byte))
        {
            if (byte.TryParse(text, System.Globalization.NumberStyles.HexNumber, null, out byte x))
            {
                return x;
            }
        }
        else if (toType == typeof(string))
        {
            return text;
        }
        return null;
    }
}

Solution 2:[2]

IMO the main improvement here would be to remove all boxing. Which means using generics throughout. Now, there's a complication there: with generics, it is hard to change types without with boxing (to convince the compiler you know what you're doing), or using meta-programming. So: I'd lean towards the latter. Something like:

public static T Convert<T>(string input)
  => TypeCache<T>.Convert(input);

private static TypeCache<T> {
    public static readonly Func<string, T> Convert
    = CreateConverter<T>();
}
private static Func<string, T> CreateConverter<T>()
{...}

The magic all happens in that last method. The problem is: it isn't trivial. The simplest approach should be to use reflection to discover a suitable parse method, then manually construct an Expression<Func<string, T>> by linking appropriate Expression nodes to repreresent the operation, then call Compile() to get a delegate. Another approach is to go straight to DynamicMethod and ILGenerator. Both are advanced topics.

Solution 3:[3]

This is the code I wrote and used in my company's project really well.

You can try to use this:

/// <summary>
/// Method : Simply Universal Type Converter
/// Requirement : C# 7.0+
/// Created by : Byungho Park(Tapegawui) in South Korea.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="val">Original value</param>
/// <param name="rfrom">(Optional)Character(s) want to replace from</param>
/// <param name="rto">(Optional)Character(s) will be replace to</param>
/// <returns></returns>
public static T Cast<T>(dynamic val, string rfrom = "", string rto = "") where T : IConvertible
{
    try
    {
        // Convert null to empty else 0
        if (val is null || val.Equals(DBNull.Value))
        {
            if (typeof(T) == typeof(string) || typeof(T) == typeof(DateTime))
            {
                val = string.Empty;
            }
            else if (typeof(T) == typeof(bool))
            {
                val = false;
            }
            else
            {
                val = 0;
            }
        }
        else
        {
            // Replace string given parameter from a to b
            if (typeof(T) == typeof(string) && rfrom == "" & rto.Length > 0)
            {
                if (val.ToString().Length == 0)
                {
                    val = rto;
                }
            }
            else if (typeof(T) == typeof(string) && rto.Length > 0)
            {
                val = (string)val.ToString().Replace(rfrom, rto);
            }
        }

        // Convert type on this block finally
        return (T)Convert.ChangeType(val, typeof(T));
    }
    catch (Exception)
    {
        return default(T);
    }
}

And usage examples:

using System;

int vint = 10;
int vint2 = vint;
string vstr = "1000000";
string vdcm = "123456789123456789";

for (int i = 1; i <= vint; i++)
{
    vint2 += i;
}
Console.WriteLine($"Adding int with loop : {vint2} from {vint}\n");

string tint = Cast<string>(vint);
for (int i = 1; i <= vint; i++)
{
    tint += i;
}
Console.WriteLine($"Adding string with loop : {tint} from {vint}\n");

long tlong = Cast<long>(vstr);
tlong *= tlong;
Console.WriteLine($"Multiply long : {tlong} from {vstr}\n");

double tdbl = Cast<double>(vdcm);
for (int i = 1; i <= vint; i++)
{
    tdbl *= i;
}
Console.WriteLine($"Multiply double with loop : {tdbl} from {vdcm}\n");

decimal tdcm = Cast<decimal>(vdcm);
for (int i = 1; i <= vint; i++)
{
    tdcm *= i;
}
Console.WriteLine($"Multiply decimal with loop : {tdcm} from {vdcm}\n");

string ns = null;
Console.WriteLine($"Null string : {Cast<string>(ns)}\n");

int? ni = null;
Console.WriteLine($"Null int : {Cast<int>(ni)}\n");

long? nl = null;
Console.WriteLine($"Null long : {Cast<long>(nl)}\n");

double? ndbl = null;
Console.WriteLine($"Null double : {Cast<double>(ndbl)}\n");

decimal? nd = null;
Console.WriteLine($"Null decimal : {Cast<decimal>(nd)}\n");

string tb = "true";
Console.WriteLine($"Convert string to boolean : {Cast<bool>(tb)}\n");

bool? nb = null;
Console.WriteLine($"Null boolean : {Cast<bool>(nb)}\n");

// -----------------------
// From Microsoft examples
double d = -2.345;
int t = Cast<int>(d);

Console.WriteLine($"The double value {d} when converted to an int becomes {t}\n");

string s = "98/12/12";
DateTime dt = Cast<DateTime>(s);

Console.WriteLine($"The string value {s} when converted to a Date becomes {dt}\n");
// -----------------------

// ------------------------------------------
// Replace some character(s) with string type
string rs = "Replace this string with x to y.";
Console.WriteLine($"{Cast<string>(rs, " ", "_")}\n");

Console.WriteLine($"{Cast<string>("abcd", "", "abc")}\n");
Console.WriteLine($"{Cast<string>("", "", "abc")}\n");

string rs3 = "Replace this string from x to y.";
string ts = "string";
Console.WriteLine($"{Cast<string>(rs3, ts, ts.ToUpper())}\n");

Console.WriteLine($"Replace int character with string : {Cast<string>(vstr, "0", "1")}\n");
// ------------------------------------------

Console.WriteLine("Press any key to close...");
Console.ReadKey();

In addition, output results:

Adding int with loop : 65 from 10

Adding string with loop : 1012345678910 from 10

Multiply long : 1000000000000 from 1000000

Multiply double with loop : 4.479999963712E+23 from 123456789123456789

Multiply decimal with loop : 447999996371199995923200 from 123456789123456789

Null string :

Null int : 0

Null long : 0

Null double : 0

Null decimal : 0

Convert string to boolean : True

Null boolean : False

The double value -2.345 when converted to an int becomes -2

The string value 98/12/12 when converted to a Date becomes 1998-12-12 ?? 12:00:00

Replace_this_string_with_x_to_y.

abcd

abc

Replace this STRING from x to y.

Replace int character with string : 1111111

Press any key to close...

Solution 4:[4]

public static class StringExtensions
{
    public static TDest ConvertStringTo<TDest>(this string src)
    {
        if (src == null)
        {
            return default(TDest);
        }

        try
        {
            return ChangeType<TDest>(src);
        }
        catch
        {
            return default(TDest);
        }
    }

    private static T ChangeType<T>(string value)
    {
        var t = typeof(T);
        if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
        {
            t = Nullable.GetUnderlyingType(t);
        }

        return (T)Convert.ChangeType(value, t);
    }
}

Solution 5:[5]

Using runtime infrastructure to determine types and perform conversions between them as proposed by the other answers is very convenient and probably what you are looking for.

If, however, you need more control over your conversion (or rather parsing), e.g. because you get weird input formats that require pre-processing, may I suggest the following class.

It lets you provide a parser method for each type you register. The types from your question with their nullable cousins come pre-registered in the constructor, but you can also add any other method to the dictionary as your input data requires:

public delegate bool Parser<T>(string input, out T output);

public class Parsers
{
    private delegate bool UntypedParser(string input, out object output);

    private Dictionary<Type, UntypedParser> _parsersByType = new Dictionary<Type, UntypedParser>();

    /// <summary>
    /// Creates a <see cref="Parsers"/> instance pre-populated with parsers for the most common types.
    /// </summary>
    public static Parsers CreateDefault()
    {
        Parsers result = new Parsers();
        result.AddParser<string>((string input, out string output) => { output = input; return true; });
        result.AddParserForNullable<bool>(bool.TryParse);
        result.AddParserForNullable<byte>(byte.TryParse);
        result.AddParserForNullable<DateTime>(DateTime.TryParse);
        result.AddParserForNullable<decimal>(decimal.TryParse);
        result.AddParserForNullable<double>(double.TryParse);
        result.AddParserForNullable<float>(float.TryParse);
        result.AddParserForNullable<int>(int.TryParse);
        result.AddParserForNullable<short>(short.TryParse);
        return result;
    }

    /// <summary>
    /// Registers a parser for the given type T
    /// </summary>
    public void AddParser<T>(Parser<T> parser)
    {
        _parsersByType[typeof(T)] = (string input, out object output) => ParseObject(parser, input, out output);
    }

    /// <summary>
    /// Registers a parser for the given type T as well as <see cref="Nullable{T}"/>
    /// </summary>
    public void AddParserForNullable<T>(Parser<T> parser)
        where T : struct
    {
        _parsersByType[typeof(T)] = (string input, out object output) => ParseObject(parser, input, out output);
        _parsersByType[typeof(T?)] = (string input, out object output) => ParseNullable(parser, input, out output);
    }

    /// <summary>
    /// For nullable types, return null when the input is an empty string or null.
    /// </summary>
    /// <remarks>This is not called for the string-parser. Meaning an empty string is not parsed into a null value.</remarks>
    private bool ParseNullable<T>(Parser<T> parser, string input, out object output)
        where T : struct
    {
        bool result;
        if (string.IsNullOrEmpty(input))
        {
            result = true;
            output = null;
        }
        else
        {
            result = ParseObject(parser, input, out output);
        }
        return result;
    }

    /// <summary>
    /// Helper method to convert the typed output into an object (possibly boxing the value)
    /// </summary>
    private bool ParseObject<T>(Parser<T> parser, string input, out object output)
    {
        bool result = parser(input, out T typedOutput);
        output = typedOutput;
        return result;
    }

    /// <summary>
    /// Finds the parser for the given target type and uses it to parse the input.
    /// </summary>
    public object Parse<T>(string input)
    {
        Type targetType = typeof(T);
        object result;
        if (!_parsersByType.TryGetValue(targetType, out UntypedParser parser))
        {
            throw new ArgumentException($"No parser defined for type '{targetType}'.");
        }
        else if (!parser(input, out result))
        {
            throw new ArgumentException($"Cannot parse '{targetType}' from  input '{input}'.");
        }
        return result;
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 PersonalNexus
Solution 2 Marc Gravell
Solution 3
Solution 4 LP13
Solution 5