Search code examples
c#reflectiondynamicmethodinfoparameterinfo

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.


Solution

  • 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