mercredi 27 juillet 2022

Create a delegate from Methodinfo with generic parameter

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.





Aucun commentaire:

Enregistrer un commentaire