views:

422

answers:

5

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);  
+7  A: 

TypeDescriptor to rescue:

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

To integrate into TypeConverter infrastructure, implement your own TypeConverter and decorate class to be converted with it with TypeConverterAttribute

Anton Gogolev
How would you decorate DateTime with this attribute or is it decorated by default?
Lazarus
Since you need to decorate the class to be converted, wouldn't this not work for his example where he's trying to do it for built-in types?
Davy8
wow, cool. did not know about `TypeDescriptor`. could you elaborate on the use? maybe take us through a concrete usage with a framework value type, and a complex custom type? i would very much like to see how much work is involved to integrate in
johnny g
+3  A: 

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);
Thomas Levesque
How would you get from Type propType to propType.Parse(myString)? I curious to see how this works.
Lazarus
I just updated my answer
Thomas Levesque
A: 

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.

Yaakov Davis
technically speaking, he is not attempting to *infer* object type from a string. he is given the object type, and would like to go from one representation [string] to another [well-defined object instance]
johnny g
A: 

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.

Davy8
+1  A: 

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.

johnny g