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

6

29

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.

Echevarria answered 9/7, 2009 at 23:33 Comment(0)
U
51

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.

Uterine answered 9/7, 2009 at 23:52 Comment(5)
What i am using it for is validating user input... :( so thats why i didn't want to use the changetype and try/catch because who knows what the user will type...Echevarria
Heh, ok. :-) FWIW, i wouldn't worry too much about the efficiency aspect of catching exceptions thrown by ChangeType(), but if you're concerned about the style then your solution looks fine. Note that you could probably simply the API by boiling it down to a single generic method, where T is specified implicitly by the out parameter.Uterine
I'm all for simplicity, what do you mean by "where T is specified implicitly by the out parameter"??Echevarria
do you mean do the type test inside the delegate... if so the reason why i did it out side was for performance, meaning that once I do the test and return a delegate I can run the delegate as many times as I want with minimal performance side effects...Echevarria
Ah, gotcha - wondered what the point of the delegate was. Hope it helps! :-)Uterine
R
18

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);
}
Raising answered 9/7, 2009 at 23:53 Comment(4)
As mentioned in the comment I gave @Shog9, the problem is because I am using this to validate user input I am worried about the overhead of generating exceptions. afaik generating an exception that you know might occur every second time the method runs is an expensive operation, hence why I was trying to use delegates instead. Would you say given that it is still worth going with the version you suggested of the delegate approach I am trying to use?Echevarria
Personally, I'd still use this. The overhead of exceptions isn't something I'd worry about in this case, especially since, if it's for user input, you won't need to call this in a tight loop (since the user can't possibly type as fast as you can handle the exception). You're going to have overhead in your switch statement, delegate calls, etc, even with the other approach, and it's horrible to maintain compared to the 6 lines I have above.Raising
I agree with Reed. Exception might take something of the order of tens of milliseconds at worst (cold); but if you're validating one user input string, that will hardly be noticeable.Gorky
result = (T)Convert.ChangeType(value, typeof(T)); You must cast from ChangeType also.Lenhart
F
7

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);
}
Fusionism answered 10/7, 2009 at 0:16 Comment(3)
If I use this approach how does this get around the need for a try/catch block??Echevarria
the exception will not be thrown because you're using TryParse. if result of your TryParse is true then the conversion will work unless you passed in a completely different generic parameter. is that your dilemma?Fusionism
sorry you cant pass a different generic parameter because you have an if statement on typeof(T) .. so what exactly is your question?Fusionism
R
2

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);
    }
Robin answered 8/2, 2013 at 3:13 Comment(0)
A
1

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;
}
Amateur answered 10/7, 2009 at 3:3 Comment(1)
if you test it, you can do over 100,000 parses in 11 seconds. i compared against .net 4 using a native expression tree which took 14 seconds (though might not reflect perf when .net 4 is released)Amateur
K
0

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...
Koah answered 29/4, 2022 at 2:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.