C#: Dynamic parse from System.Type

C#.NetType Conversion

C# Problem Overview


I have a Type, a String and an Object.

Is there some way I can call the parse method or convert for that type on the string dynamically?

Basically how do I remove the if statements in this logic

object value = new object();    
String myString = "something";
Type propType = p.PropertyType;

if(propType == Type.GetType("DateTime"))
{
    value = DateTime.Parse(myString);
}

if (propType == Type.GetType("int"))
{
    value = int.Parse(myString);
}

And do someting more like this.

object value = new object();
String myString = "something";
Type propType = p.PropertyType;


//this doesn't actually work
value = propType .Parse(myString);  

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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);

Solution 3 - C#

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);

Solution 4 - C#

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 (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].

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

Solution 5 - C#

It's technically impossible to look at a string, and know for certain which type it represents.

So, for any generic approach, you'll need at least:

  1. the string to be parsed
  2. the type used for parsing.

Take a look at the static Convert.ChangeType() method.

Solution 6 - C#

It seems like what you want to do (at least if the types involved are types to which you can't modify the source) would require duck typing which isn't in C#

If you need to do this a lot, I would wrap the logic in a class or method that you can pass "myString" and "propType" to and it would return value. In that method you'd just do the if chain you have above and return the value when it finds one that matches. You'd have to manually list out all the possible types still, but you'd only have to do it once.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionctrlShiftBryanView Question on Stackoverflow
Solution 1 - C#Anton GogolevView Answer on Stackoverflow
Solution 2 - C#Thomas LevesqueView Answer on Stackoverflow
Solution 3 - C#Randall BorckView Answer on Stackoverflow
Solution 4 - C#johnny gView Answer on Stackoverflow
Solution 5 - C#user286353View Answer on Stackoverflow
Solution 6 - C#Davy8View Answer on Stackoverflow