views:

539

answers:

4

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.

+2  A: 

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.

Shog9
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...
vdh_ant
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.
Shog9
I'm all for simplicity, what do you mean by "where T is specified implicitly by the out parameter"??
vdh_ant
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...
vdh_ant
Ah, gotcha - wondered what the point of the delegate was. Hope it helps! :-)
Shog9
+1  A: 

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);
}
Reed Copsey
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?
vdh_ant
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.
Reed Copsey
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.
Pavel Minaev
A: 

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);
}
Stan R.
If I use this approach how does this get around the need for a try/catch block??
vdh_ant
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?
Stan R.
sorry you cant pass a different generic parameter because you have an if statement on typeof(T) .. so what exactly is your question?
Stan R.
A: 

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;
}
Darren Kopp
I'm trying to avoid reflection due to performance
vdh_ant
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)
Darren Kopp