Home > Software design >  Create a delegate from Methodinfo with generic parameter
Create a delegate from Methodinfo with generic parameter

Time:07-27

Based on this old but very usefull article by the legendary Jon Skeet, I wrote a few methods to genereate Action Delegates from MethodInfos for faster invokations. But I ran into an issue, where I am unable to make any progress. See the example below:

public class DataContainer
{
    public List<int> myList;

    public DataContainer()
    {
        myList = new List<int>() { 1, 2, 3, 4 };
    }
}

public static class ExtensionMethods
{
    public static void DoStuff<T>(this IList<T> items)
    {
    }
}

public class Example
{
    private Action<object> doStuffQuickly;

    public void Test()
    {
        var fieldInfo = typeof(DataContainer).GetField("myList");

        var doStuffMethod = typeof(ExtensionMethods).GetMethod("DoStuff", BindingFlags.Public | BindingFlags.Static);
        var listType = typeof(IList<>).MakeGenericType(fieldInfo.FieldType.GenericTypeArguments);
        doStuffQuickly = QuickAccess.CreateWeakActionWithOneParam(doStuffMethod, listType);

        var data = new DataContainer();
        var list = fieldInfo.GetValue(data);

        doStuffQuickly(list);
    }
}

public static class QuickAccess
{
    private static MethodInfo _weak1ParamActionCreator;
    private static MethodInfo Weak1ParamActionCreator => _weak1ParamActionCreator
        ??= typeof(QuickAccess).GetMethod(nameof(CreateWeakExplicit1ParamAction), BindingFlags.Static | BindingFlags.NonPublic);

    public static Action<object> CreateWeakActionWithOneParam(MethodInfo methodInfo, Type paramType0 = null)
    {
        var parameters = methodInfo.GetParameters();
        paramType0 ??= parameters[0].ParameterType;
        var creationMethod = Weak1ParamActionCreator.MakeGenericMethod(paramType0);
        return (Action<object>)creationMethod.Invoke(null, new object[] { methodInfo });
    }

    private static Action<object> CreateWeakExplicit1ParamAction<TValue>(MethodInfo methodInfo)
    {
        var action = CreateStrongExplicit1ParamAction<TValue>(methodInfo);
        return (object value) => action((TValue)value);
    }

    private static Action<TValue> CreateStrongExplicit1ParamAction<TValue>(MethodInfo methodInfo)
    {
        //Debug.Log(methodInfo.GetParameters()[0].ParameterType.Name   " <-> "   typeof(TValue).Name);
        //Debug.Log(PrintTypes(methodInfo.GetParameters()[0].ParameterType.GenericTypeArguments)   " <-> "
        //      PrintTypes(typeof(TValue).GenericTypeArguments));
        return (Action<TValue>)Delegate.CreateDelegate(typeof(Action<TValue>), methodInfo);
    }

    private static string PrintTypes(Type[] types)
    {
        return string.Join(", ", types.Select(t => t.Name));
    }
}

This fails with ArgumentException: method arguments are incompatible when trying to create the delegate. The commented logs print IList`1 <-> IList`1 and T <-> Int32, which made me think that maybe

var listType = typeof(IList<>).MakeGenericType(fieldInfo.FieldType.GenericTypeArguments);

should instead simply be

var listType = typeof(IList<>);

but that is causing an InvalidOperationException: Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.

I don't know how to proceed from here. Any help will be appreciated.

CodePudding user response:

If I understand the code correctly, listType would be a IList<int>, so DoStuff would be DoStuff<IList<int>>. I would expect it to be DoStuff<int>

So the correct version should be

var listItemType = fieldInfo.FieldType.GenericTypeArguments[0]; 
doStuffMethod = doStuffMethod.MakeGenericMethod(listItemType);
doStuffQuickly = QuickAccess.CreateWeakActionWithOneParam(doStuffMethod);

I would highly recommend running your code in a debugger and inspect your values in every step to confirm all your reflection types look like you expect. Reflection is difficult, so it is really useful to be able to confirm any assumptions.

  • Related