Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 749
Nik Kalyani Avatar asked Jul 10 '11 04:07

Nik Kalyani


3 Answers

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

like image 116
Vitaliy Avatar answered Nov 19 '22 20:11

Vitaliy


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);
}
like image 28
Nahydrin Avatar answered Nov 19 '22 21:11

Nahydrin


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

like image 26
C.Evenhuis Avatar answered Nov 19 '22 20:11

C.Evenhuis