Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 329
vdh_ant Avatar asked Jul 09 '09 23:07

vdh_ant


3 Answers

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.

like image 119
Shog9 Avatar answered Sep 25 '22 16:09

Shog9


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); } 
like image 30
Reed Copsey Avatar answered Sep 21 '22 16:09

Reed Copsey


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);
}
like image 42
Stan R. Avatar answered Sep 25 '22 16:09

Stan R.