How to do automatic type conversion for parameters when invoking a method using reflection in C#?
Asked Answered
S

3

8

I have a need to invoke methods on a type through reflection using C#.

At run-time, my data will consist of a Dictionary containing name/value pairs. The names in the Dictionary will correspond to parameter names on the method I will invoke. Also, at run-time, I will have an arbitrary assembly qualified type name and a method name. At design time, I will have no knowledge of the type and the method other than that the method will accept a variable number of parameters of type int, string, DateTime, bool, int[], string[], DateTime[] or bool[].

I have no problem getting to the point where I can create an instance of the type using reflection and invoke the method. I am stuck at the point where I have to convert the string values in my dictionary to the appropriate type needed by the method when I call:

someMethodInfo.Invoke(instance, new [] { ... })

I know that I need to probably enumerate through MethodInfo.GetParameters() and perform the type conversion for each parameter. What I am trying to figure out is how to do this, and ideally, how to do it efficiently.

My research so far has involved digging into the MVC source code as it does something similar when passing form values to an ActionMethod. I found ActionMethodDispatcher but it uses LINQ Expressions, with which I am unfamiliar.

I also looked at similar questions on SO, but did not find anything that answers my question.

I would welcome any pointers to a solution.

Su answered 10/7, 2011 at 4:44 Comment(6)
There may be better ways to go about it but I've used System.Convert for similar needs. Dealing with nullabe types adds some additional complexity as well.Alyosha
Another option for more arbitrary conversion is the TypeConverter, though I'm not sure how this would work with arrays.Orpiment
@Alyosha Yes, I think System.Convert will be my fallback if there is nothing more elegant. It just seems there might be a more efficient way than looping through each parameter type and using System.Convert in a switch(...) to do individual type conversions.Su
@Anna ParameterInfo.ParameterType.IsArray does tell me if an array is expected. I suppose I could create an array and add each value using TypeConverter to the array before passing it as a parameter.Su
@Nik Kalyani Since you will have the Type of the param from the MethodInfo you can use ChangeType to do the conversion, which will prevent having to use a giant switch, but I'm also curious to know if there is a better solution.Alyosha
What for do the values in the dictionary have? Are they all strings?Jemma
B
5

Here is some code which can be used for parameters conversion:

public object ConvertSingleItem(string value, Type newType)
{
    if (typeof(IConvertible).IsAssignableFrom(newType))
    {
        return Convert.ChangeType(value, newType);
    }
    else
    {
        // TODO: Add custom conversion for non IConvertible types
        var converter = CustomConvertersFactory.GetConverter(newType);
        return converter.Convert(value);
    }
}

public object ConvertStringToNewNonNullableType(string value, Type newType)
{
    // Do conversion form string to array - not sure how array will be stored in string
    if (newType.IsArray)
    {
        // For comma separated list
        Type singleItemType = newType.GetElementType();

        var elements = new ArrayList();
        foreach (var element in value.Split(','))
        {
            var convertedSingleItem = ConvertSingleItem(element, singleItemType);
            elements.Add(convertedSingleItem);
        }
        return elements.ToArray(singleItemType);
    }
    return ConvertSingleItem(value, newType);
}

public object ConvertStringToNewType(string value, Type newType)
{
    // If it's not a nullable type, just pass through the parameters to Convert.ChangeType
    if (newType.IsGenericType && newType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
        if (value == null)
        {
            return null;
        }
        return ConvertStringToNewNonNullableType(value, new NullableConverter(newType).UnderlyingType);
    }
    return ConvertStringToNewNonNullableType(value, newType);
}

public object CallMethod(object instance, MethodInfo methodInfo, Dictionary<string, string> parameters)
{
    var methodParameters = methodInfo.GetParameters();

    var parametersForInvocation = new List<object>();
    foreach (var methodParameter in methodParameters)
    {
        string value;
        if (parameters.TryGetValue(methodParameter.Name, out value))
        {
            var convertedValue = ConvertStringToNewType(value, methodParameter.ParameterType);
            parametersForInvocation.Add(convertedValue);
        }
        else
        {
            // Get default value of the appropriate type or throw an exception
            var defaultValue = Activator.CreateInstance(methodParameter.ParameterType);
            parametersForInvocation.Add(defaultValue);
        }
    }
    return methodInfo.Invoke(instance, parametersForInvocation.ToArray());
}

It supports Primitive types, Nullables and Arrays of primitive types. In the case when you going to use types which doesn't support IConvertible interface - it is better to implement custom converters for each individual type.

It can be written in more elegant way with Linq.

Vitaliy

Balch answered 11/7, 2011 at 8:29 Comment(0)
S
4

The value you want to convert should be an object, otherwise conversions outside the standard types will not work. You can easily convert between types like so:

object value = false; // false
Type chType = typeof(String); // System.String
object newValue = Convert.ChangeType(value, chType); // "false"

It's as easy as that. If you want a method:

public object ConvertType(object value, Type conversionType)
{
    //Check if type is Nullable
    if (conversionType.IsGenericType &&
        conversionType.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        //If the type is Nullable and the value is null
        //Just return null
        if (value == null)
        {
            return null;
        }

        //Type is Nullable and we have a value, override conversion type to underlying
        //type for the Nullable to avoid exception in Convert.ChangeType
        var nullableConverter = new NullableConverter(conversionType);
        conversionType = nullableConverter.UnderlyingType;
    }

    return Convert.ChangeType(value, conversionType);
}
Serene answered 11/7, 2011 at 8:38 Comment(1)
as I mentioned in my comments nullable types do need some special case handling. I've modified your method to reflect how you could go about thatAlyosha
U
2

Perhaps a nice way to manage "converters" is to maintain a Dictionary<Type, IMyTypeConverter> - where IMyTypeConverter has a object Convert(string value).

Unanimity answered 10/7, 2011 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.