views:

957

answers:

3

How can I convert TO a Nullable from a String using reflection?

I have the following code to convert TO almost any value type given almost any value. There is quite a bit of code above this to use the IsAssignableFrom, etc. so this is the last resort.

MethodInfo parse = t.GetMethod("Parse", new Type[] { typeof(string) });

if (parse != null)
{
    object parsed = parse.Invoke(null, new object[] { value.ToString() });
    return (T)parsed;
}
else
{
    throw new InvalidOperationException("The value you specified is not a valid " + typeof(T).ToString());
}

The problem comes when I want to to convert to a nullable type like long?.

Obviously, the long? class does not have a parse method.

How do I extract the parse method from the nullable's template type?

EDIT:

Here is a short battery of tests I'm trying to pass:

[Test]
public void ConverterTNullable()
{
    Assert.That((int?)1, Is.EqualTo(Converter<int?>.Convert(1)));
    Assert.That((int?)2, Is.EqualTo(Converter<int?>.Convert(2.0d)));
    Assert.That(3, Is.EqualTo(Converter<long>.Convert(3)));

    Assert.That((object)null, Is.EqualTo(Converter<long?>.Convert("")));
    Assert.That((object)null, Is.EqualTo(Converter<long?>.Convert(null)));
    Assert.That((object)null, Is.EqualTo(Converter<long?>.Convert(DBNull.Value)));

    Assert.That((long)1, Is.EqualTo(Converter<long?>.Convert("1")));
    Assert.That((long)2, Is.EqualTo(Converter<long?>.Convert(2.0)));
    Assert.That((long?)3, Is.EqualTo(Converter<long>.Convert(3)));
}

And the whole function:

/// <summary>
/// Converts any compatible object to an instance of T.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <returns>The converted value.</returns>
public static T Convert(object value)
{
    if (value is T)
    {
        return (T)value;
    }

    Type t = typeof(T);

    if (t == typeof(string))
    {
        if (value is DBNull || value == null)
        {
            return (T)(object)null;
        }
        else
        {
            return (T)(object)(value.ToString());
        }
    }
    else
    {
        if (value is DBNull || value == null)
        {
            return default(T);
        }

        if (value is string && string.IsNullOrEmpty((string)value))
        {
            return default(T);
        }

        try
        {
            return (T)value;
        }
        catch (InvalidCastException)
        {
        }

        if (Nullable.GetUnderlyingType(t) != null)
        {
            t = Nullable.GetUnderlyingType(t);
        }

        MethodInfo parse = t.GetMethod("Parse", new Type[] { typeof(string) });

        if (parse != null)
        {
            object parsed = parse.Invoke(null, new object[] { value.ToString() });
            return (T)parsed;
        }
        else
        {
            throw new InvalidOperationException("The value you specified is not a valid " + typeof(T).ToString());
        }
    }
}
+4  A: 

I would probably use the TypeConverter in this case, and Nullable.GetUnderlyingType(); example on the way...

    static void Main()
    {
        long? val1 = Parse<long?>("123");
        long? val2 = Parse<long?>(null);
    }

    static T Parse<T>(string value)
    {
        return (T) TypeDescriptor.GetConverter(typeof(T))
            .ConvertFrom(value);
    }
Marc Gravell
btw - if you really want the underlying type, try Nullable.GetUnderlyingType(typeof(T))
Marc Gravell
@Marc This code probably saved me a day of work.
Chris Dwyer
+2  A: 

This should to the job. (Code is embedded in an extension method for simplicity, though it may not be what you want.)

public static T? ParseToNullable<T>(this string value) where T : struct
{
    var parseMethod = typeof(T).GetMethod("Parse", new Type[] { typeof(string) });
    if (parseMethod == null)
        return new Nullable<T>();

    try
    {
        var value = parseMethod.Invoke(null, new object[] { value.ToString() });
        return new Nullable<T>((T)value);
    }
    catch
    {
        return new Nullable<T>();
    }
}

Now, if you want the generic type paramter itself to be the nullable type rather than the underlying one (I don't really see an advantage in this), you can make use of:

Nullable.GetUnderlyingType(typeof(T))

as Marc Gravell suggested, and it would only require a few minor modifications to the code.

Noldorin
I'd modify the last bit...the way your code reads you'll never get a null value, which makes the Nullable kind of meaningless. You probably need to trap for an exception in your Invoke (which means that TryParse is likely a better method to use) and return null if you encounter one.
Adam Robinson
Close, but not quite.I'm actually trying to write a generic converter. Im trying to do this: Converter<long?>.Convert("123")
John Gietzen
@Adam: Yeah, you're absolutely right. Looking at it the second time, it was clearly silliness. Guess I was somewhat distracted during the writing of the original function code. :P
Noldorin
@John: Did you see the updated answer? If you want to use a static Converter class instead, it shouldn't really be any trouble to convert to that form.
Noldorin
Note: The alternative to exception handling in the function in my answer is to use TryParse, as Adam Robinson suggested - though I'm not sure this necessarily offers any advantages (except perhaps a performance increase, which may not be relevant).
Noldorin
A: 

I added this little bit:

if (Nullable.GetUnderlyingType(t) != null)
{
    t = Nullable.GetUnderlyingType(t);
}

MethodInfo parse = t.GetMethod("Parse", new Type[] { typeof(string) });

Thanks everyone!

John Gietzen
That won't be enough to return a nullable type, only to retrieve the underlying type and use that.
Noldorin
yea, but when I do this: return (T)parse.Invoke(...) it will.
John Gietzen
Oh, I didn't realise it would allow you to cast like that. Fair enough.
Noldorin
Btw, it's customary to upvote any answer that helped you, even if the end answer is technically yours. (In particular, Mark Gravell's in this case.)
Noldorin
Both have been upvoted, since both helped.
John Gietzen