C#: Dynamic parse from System.Type

TypeDescriptor to the rescue!:

var converter = TypeDescriptor.GetConverter(propType);
var result = converter.ConvertFrom(myString);

All primitive types (plus Nullable<TPrimitive>, and numerous other built-in types) are integrated into the TypeConverter infrastructure already, and are thus supported 'out-of-the-box'.

To integrate a custom type into the TypeConverter infrastructure, implement your own TypeConverter and use TypeConverterAttribute to decorate the class to be converted, with your new TypeConverter


This should work for all primitive types, and for types that implement IConvertible

public static T ConvertTo<T>(object value)
{
    return (T)Convert.ChangeType(value, typeof(T));
}

EDIT : actually in your case, you can't use generics (not easily at least). Instead you could do that :

object value = Convert.ChangeType(myString, propType);

I ran into this problem and this is how I solved it:

value = myString;
var parse = propType.GetMethod("Parse", new[] { typeof(string) });
if (parse != null) {
  value = parse.Invoke(null, new object[] { value });
}

...and it worked for me.

To sum it up, you are trying to find a static "Parse" method on the object type that takes only one string as an argument. If you find such a method, then invoke it with the string parameter you are trying to convert. Since p is the PropertyInfo for my type, I ended this method by setting my instance with the value as follows:

p.SetValue(instance, value, null);

Depends on what you would like to accomplish.

1) if you are simply trying to clean up your code, and remove repetitive type checking, then what you want to do is centralize your checks in a method, comme

public static T To<T> (this string stringValue)
{
    T value = default (T);

    if (typeof (T) == typeof (DateTime))
    {
        // insert custom or convention System.DateTime 
        // deserialization here ...
    }
    // ... add other explicit support here
    else
    {
        throw new NotSupportedException (
            string.Format (
            "Cannot convert type [{0}] with value [{1}] to type [{2}]." + 
            " [{2}] is not supported.",
            stringValue.GetType (),
            stringValue,
            typeof (T)));
    }

    return value;
}

2) if you would like something more generalized for basic types, you could try something like Thomas Levesque suggests - though in truth, I have not attempted this myself, I am unfamiliar with [recent?] extensions to Convert. Also a very good suggestion.

3) in fact, you probably want to merge both 1) and 2) above into a single extension that would enable you to support basic value conversion, and explicit complex type support.

4) if you want to be completely "hands-free", then you could also default to plain old Deserialization [Xml or Binary, either/or]. Of course, this constrains your input - ie all input must be in an approriate Xml or Binary format. Honestly, this is probably overkill, but worth mentioning.

Of course, all of these methods do essentially the same thing. There is no magic in any of them, at some point someone is performing a linear lookup [whether it is an implicit look up through sequential if-clauses or under the hood via .Net conversion and serialization facilities].

5) if you want to improve performance, then what you want to do is improve the "lookup" part of your conversion process. Create an explicit "supported types" list, each type corresponding to an index in an array. Instead of specifying the Type on a call, you then specify the index.

EDIT: so, while linear look up is neat and speedy, it also occurs to me it would be even faster if consumer simply obtained conversion functions and invoked them directly. That is, consumer knows what type it would like to convert to [this is a given], so if it needs to convert many items at one time,

// S == source type
// T == target type
public interface IConvert<S>
{
    // consumers\infrastructure may now add support
    int AddConversion<T> (Func<S, T> conversion);

    // gets conversion method for local consumption
    Func<S, T> GetConversion<T> ();

    // easy to use, linear look up for one-off conversions
    T To<T> (S value);
}

public class Convert<S> : IConvert<S>
{

    private class ConversionRule
    {
        public Type SupportedType { get; set; }
        public Func<S, object> Conversion { get; set; }
    }

    private readonly List<ConversionRule> _map = new List<ConversionRule> ();
    private readonly object _syncRoot = new object ();

    public void AddConversion<T> (Func<S, T> conversion)
    {
        lock (_syncRoot)
        {
            if (_map.Any (c => c.SupportedType.Equals (typeof (T))))
            {
                throw new ArgumentException (
                    string.Format (
                    "Conversion from [{0}] to [{1}] already exists. " +
                    "Cannot add new conversion.", 
                    typeof (S), 
                    typeof (T)));
            }

            ConversionRule conversionRule = new ConversionRule
            {
                SupportedType = typeof(T),
                Conversion = (s) => conversion (s),
            };
            _map.Add (conversionRule);
        }
    }

    public Func<S, T> GetConversion<T> ()
    {
        Func<S, T> conversionMethod = null;

        lock (_syncRoot)
        {
            ConversionRule conversion = _map.
                SingleOrDefault (c => c.SupportedType.Equals (typeof (T)));

            if (conversion == null)
            {
                throw new NotSupportedException (
                    string.Format (
                    "Conversion from [{0}] to [{1}] is not supported. " + 
                    "Cannot get conversion.", 
                    typeof (S), 
                    typeof (T)));
            }

            conversionMethod = 
                (value) => ConvertWrap<T> (conversion.Conversion, value);
        }

        return conversionMethod;
    }

    public T To<T> (S value)
    {
        Func<S, T> conversion = GetConversion<T> ();
        T typedValue = conversion (value);
        return typedValue;
    }

    // private methods

    private T ConvertWrap<T> (Func<S, object> conversion, S value)
    {
        object untypedValue = null;
        try
        {
            untypedValue = conversion (value);
        }
        catch (Exception exception)
        {
            throw new ArgumentException (
                string.Format (
                "Unexpected exception encountered during conversion. " +
                "Cannot convert [{0}] [{1}] to [{2}].",
                typeof (S),
                value,
                typeof (T)),
                exception);
        }

        if (!(untypedValue is T))
        {
            throw new InvalidCastException (
                string.Format (
                "Converted [{0}] [{1}] to [{2}] [{3}], " +
                "not of expected type [{4}]. Conversion failed.",
                typeof (S),
                value,
                untypedValue.GetType (),
                untypedValue,
                typeof (T)));
        }

        T typedValue = (T)(untypedValue);

        return typedValue;
    }

}

and it would be used as

// as part of application innitialization
IConvert<string> stringConverter = container.Resolve<IConvert<string>> ();
stringConverter.AddConversion<int> (s => Convert.ToInt32 (s));
stringConverter.AddConversion<Color> (s => CustomColorParser (s));

...

// a consumer elsewhere in code, say a Command acting on 
// string input fields of a form
// 
// NOTE: stringConverter could be injected as part of DI
// framework, or obtained directly from IoC container as above
int someCount = stringConverter.To<int> (someCountString);

Func<string, Color> ToColor = stringConverter.GetConversion <Color> ();
IEnumerable<Color> colors = colorStrings.Select (s => ToColor (s));

I much prefer this latter approach, because it gives you complete control over conversion. If you use an Inversion of Control [IoC] container like Castle Windsor or Unity, then injection of this service is done for you. Also, because it is instance based, you can have multiple instances, each with its own set of conversion rules - if for instance, you have multiple user controls, each generating it's own DateTime or other complex string format.

Heck, even if you wanted to support multiple conversion rules for a single target type, that is also possible, you simply have to extend method parameters to specify which one.