Search code examples
c#.netgenericscasting

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.


Solution

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