vendredi 28 mai 2021

Reflection: How to infer a method's generic type parameters?

In C# you can call a generic method via reflection with code like this:

public class Sample {
    public void Foo<T>() { Console.WriteLine(typeof(T).Name); }
}

MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.Foo));
MethodInfo generic = method.MakeGenericMethod(typeof(int));
generic.Invoke(new Sample(), new object[0]);

I want to do something more complicated: generic parameter inference, or something similar to overload resolution. I want a Resolve method that figures out the generic type parameters in order to construct an instantiated generic method:

public static MethodInfo Resolve(MethodInfo genericMI, params Type[] argTypes)
{
    // for example, if genericMI is a reference to 
    // `Sample.Foo2<,>` (below) and argTypes is 
    // `new[] { typeof(List<(int, float)>) }`, this 
    // method would return a `MethodInfo` for 
    // `Sample.Foo2<int, float>`. But how?
}

public class Sample {
    public void Foo1<T>(IEnumerable<T> data) { }
    public void Foo2<T, U>(IEnumerable<(T, U)> data) { }
    public void Foo3<T, U>(IEnumerable<U> list, (T, U) tuple) where U: struct { }
}

// EXAMPLES:
// Returns MethodInfo for Foo1<(int, float)>
var foo2 = Resolve(typeof(Sample).GetMethod("Foo1"), typeof(List<(int, float)>));

// Returns MethodInfo for Foo2<int, float>
var foo2 = Resolve(typeof(Sample).GetMethod("Foo2"), typeof(List<(int, float)>));

// Returns MethodInfo for Foo3<int, byte>
var foo3 = Resolve(typeof(Sample).GetMethod("Foo3"), typeof(List<byte>), typeof((int, byte)));

// Returns null
var failA = Resolve(typeof(Sample).GetMethod("Foo3"), typeof(List<byte>), typeof((int, float)));

// Returns null
var failB = Resolve(typeof(Sample).GetMethod("Foo3"), typeof(List<Regex>), typeof((int, Regex)));

If it makes things simpler, for my application I only need to resolve these type parameters based on a single parameter (although the target method actually takes two parameters).

Why I'm asking this question

I'm writing a serialization system in which users can "plug in" serializers for specific types. But I think users should be able to provide generic methods that can handle families of types like UserType<X,Y>. So my system will have a list of generic methods (from multiple user-defined classes), and I need to figure out which one of the methods is actually possible to call given the parameter type:

public static MethodInfo Resolve(MethodInfo[] genericMIs, params Type[] argTypes)
{
    foreach (var mi in genericMIs) {
        var resolved = Resolve(mi, argTypes);
        if (resolved != null)
            return resolved;
    }
    return null;
}

I will then construct a delegate via Delegate.CreateDelegate(..., resolved) and use some other tricks to cache the delegate and invoke it like a normal method to achieve good performance, as the same delegate will often be re-used to serialize or deserialize thousands of objects.





Aucun commentaire:

Enregistrer un commentaire