vdh_ant
vdh_ant

Reputation: 13156

How can I convert to a specific type in a generic version of TryParse()?

I have the following scenario where I want to pass in string and a generic type:

public class Worker {
    public void DoSomeWork<T>(string value) 
        where T : struct, IComparable<T>, IEquatable<T> { ... }
}

At some point along the way I need to convert the string value to its T value. But I don't want to do a straight convert as I need to perform some logic if the string cannot be converted to type T.

I was thinking that I could try using Convert.ChangeType() but this has the problem that if it doesn't convert it will throw an exception and I will be running the DoSomeWork() method often enough to not have to rely on a try/catch to determine whether the convert is valid.

So this got me thinking, I know that I will be working with numeric types, hence T will be any of the following: int, uint, short, ushort, long, ulong, byte, sbyte, decimal, float, double. Knowing this I thought that it might be possible to come up with a faster solution working with the fact that I know I will be using numeric types (note if T isn't a numeric type I throw an exception)...

public class NumericWorker {
    public void DoSomeWork<T>(string value) 
        where T : struct, IComparable<T>, IEquatable<T> 
    { 
        ParseDelegate<T> tryConverter = 
           SafeConvert.RetreiveNumericTryParseDelegate<T>();
        ... 
    }
}


public class SafeConvert
{
    public delegate bool ParseDelegate<T>(string value, out T result);

    public static ParseDelegate<T> RetreiveNumericTryParseDelegate<T>()
        where T : struct, IComparable<T>, IEquatable<T>
    {
        ParseDelegate<T> tryParseDelegate = null;

        if (typeof(T) == typeof(int))
        {
           tryParseDelegate = (string v, out T t) =>
              {
                 int typedValue; 
                 bool result = int.TryParse(v, out typedValue);
                 t = result ? (T)typedValue : default(T); 
                 //(T)Convert.ChangeType(typedValue, typeof(T)) : default(T);
                 return result;
              }; 
        }
        else if (typeof(T) == typeof(uint)) { ... }
        else if (typeof(T) == typeof(short)) { ... }
        else if (typeof(T) == typeof(ushort)) { ... }
        else if (typeof(T) == typeof(long)) { ... }
        else if (typeof(T) == typeof(ulong)) { ... }
        else if (typeof(T) == typeof(byte)) { ... }
        else if (typeof(T) == typeof(sbyte)) { ... }
        else if (typeof(T) == typeof(decimal)) { ... }
        else if (typeof(T) == typeof(float)) { ... }
        else if (typeof(T) == typeof(double)) { ... }

        return tryParseDelegate;
    }
}

But the above has the problem that I can't write t = result ? (T)typedValue : default(T); as the casting of typedValue to T causes issues and the only way I have been able to get around it thus far is by writing (T)Convert.ChangeType(typedValue, typeof(T)). But if I do this I am just doing another convert.

Hence I was wondering if anyone knows how I could fix this problem (if you think doing the ChangeType() is a problem) or if there is a better solution altogether that I haven't considered.

Upvotes: 29

Views: 66311

Answers (7)

Mr. X
Mr. X

Reputation: 867

This is the solution I use:

public static T ChangeType<T>(object data)
{
    return (T)(Convert.ChangeType(data, typeof(T)));
}

Upvotes: -1

Tapegawui
Tapegawui

Reputation: 11

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

You can try to use this simple also:

/// <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...

Upvotes: 0

Herman Schoenfeld
Herman Schoenfeld

Reputation: 8724

You can try something simple

    public static T ConvertValue<T,U>(U value) where U : IConvertible {
        return (T)ConvertValue(value, typeof(T));
    }

    public static object ConvertValue(IConvertible value, Type targetType) {
        return Convert.ChangeType(value, targetType);
    }

Upvotes: 1

Darren Kopp
Darren Kopp

Reputation: 77627

Why not just use reflection and use the built in TryParse methods? Pretty much one for every native type with the exception of Guid.

public static Parser<T> GetParser<T>(T defaultResult)
    where T : struct
{
    // create parsing method
    Parser<T> parser = (string value, out T result) =>
    {
        // look for TryParse(string value,out T result)
        var parseMethod = 
            typeof(T).GetMethods()
                     .Where(p => p.Name == "TryParse")
                     .Where(p => p.GetParameters().Length == 2)
                     .Single();

        // make parameters, leaving second element uninitialized means out/ref parameter
        object[] parameters = new object[2];
        parameters[0] = value;

        // run parse method
        bool success = (bool)parseMethod.Invoke(null, parameters);

        // if successful, set result to output
        if (!success)
        {
            result = (T)parameters[1];
        }
        else
        {
            result = defaultResult;
        }

        return success;
    };

    return parser;
}

Upvotes: 1

Stan R.
Stan R.

Reputation: 16065

ToType being the generic parameter here. This works for nullable types, just in case you needed it. You can extract your main method to be a generic converter, that will convert to any type, including nullables.

    ToType result = default(ToType);    

    result = ChangeType<ToType>(typedValue);


  private T ChangeType<T>(object o)
{
   Type conversionType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
   return (T)Convert.ChangeType(o, conversionType);
}

Upvotes: 6

Reed Copsey
Reed Copsey

Reputation: 564363

Given this:

hence T will be any of the following: int, uint, short, ushort, long, ulong, byte, sbyte, decimal, float, double.

I would recommend just using Convert.ChangeType, and not worrying about it. The only time you'll get an exception is when your string is misformatted, in which case, you can return default(T).

ie:

try
{
    result = Convert.ChangeType(value, typeof(T));
}
catch
{
    result = default(T);
}

Upvotes: 17

Shog9
Shog9

Reputation: 159590

t = result ? (T)typedValue : default(T);

Try:

t = result ? (T)(object)typedValue : default(T);

Yes, generics can be kinda annoying at times.

FWIW, I use a much simpler wrapper around Convert.ChangeType() that just does a pre-check for empty strings. Unless you're using this for un-checked user input, that'll probably be enough.

Upvotes: 51

Related Questions