Question

The function MethodInfo.MakeGenericMethod requires that you pass an array of types that correspond to the type parameters. I'm in a scenario where the type signature is non-trivial and not known until run-time. I only have the types of the arguments to the functino.

So for example here is a function signature:

static IArray<U> Map<T, U>(IArray<T> xs, Func<T, U> fxn) { ... }

I have two arguments of types t1 (e.g. IArray) and t2 (e.g. Func) known only at run-time. I want to leverage the C# type-inference algorithms at run-time, e.g. to compute T and U from t1 and t2.

MethodInfo MakeGenericMethodUsingInference(MethodInfo mi, Type t1, Type t2) {
   var typeArg1 = ??;
   var typeArg2 = ??;
   return mi.MakeGenericMethod(typeArg1, typeArg2);
}
Was it helpful?

Solution

For now I have a manual work-around that does a good enough job for my needs, but it feels like there must be a better way:

    public class TypeArgumentInferenceEngine
    {
        public Dictionary<Type, Type> ParameterToConcrete = new Dictionary<Type, Type>();

        public void MatchTypes(Type concrete, Type generic)
        {
            if (generic.IsGenericParameter)
            {
                if (ParameterToConcrete.ContainsKey(generic))
                {
                    var tmp = ParameterToConcrete[generic];
                    if (!tmp.Equals(concrete))
                        throw new Exception(String.Format("Template parameter {0} is inconsistent between {1} and {2}", generic, concrete, tmp));
                }
                else
                {
                    ParameterToConcrete.Add(generic, concrete);
                }
            }
            else if (generic.IsGenericType) 
            {
                if (!concrete.IsGenericType)
                    throw new Exception("Expected generic concrete type");
                var concreteTypeArgs = concrete.GetGenericArguments();
                var genericTypeArgs = generic.GetGenericArguments();
                if (concreteTypeArgs.Length != genericTypeArgs.Length)
                    throw new Exception("Mismatched number of generic argument types");
                for (int i = 0; i < genericTypeArgs.Length; ++i)
                    MatchTypes(concreteTypeArgs[i], genericTypeArgs[i]);
            }
        }

        public static Dictionary<Type, Type> InferTypeParameters(Type[] concreteTypes, Type[] genericTypes)
        {
            var engine = new TypeArgumentInferenceEngine();
            int n = concreteTypes.Length;
            if (n != genericTypes.Length) throw new ArgumentException("Both input arrays have to be the same size");
            for (int i = 0; i < n; ++i)
                engine.MatchTypes(concreteTypes[i], genericTypes[i]);
            foreach (var t in engine.ParameterToConcrete.Keys)
                if (!t.IsGenericParameter)
                    throw new Exception("Expected a generic type parameter");
            return engine.ParameterToConcrete;
        }

        public static MethodInfo MakeGenericMethodFromArgTypes(MethodInfo mi, params Type[] argTypes)
        {
            if (!mi.IsGenericMethodDefinition)
                return mi;
            var typeParams = mi.GetGenericArguments();
            var paramTypes = mi.GetParameters().Select(p => p.ParameterType).ToArray();
            var typeLookup = InferTypeParameters(argTypes, paramTypes);
            var typeArgs = new List<Type>();
            foreach (var p in typeParams) 
            {
                if (!typeLookup.ContainsKey(p))
                    throw new Exception(String.Format("Type parameter {0} is not bound: ", p));
                typeArgs.Add(typeLookup[p]);
            }
            return mi.MakeGenericMethod(typeArgs.ToArray());
        }
    }

    public static void TestTypeDeduction()
    {
        var t1 = typeof(IArray<int>);
        var t2 = typeof(Func<int, bool>);

        // Map has the type "Func<IArray<T> xs, Func<T, U>, IArray<U>>"
        var genericMethod = typeof(Ops).GetMethod("Map");
        var concreteMethod = TypeArgumentInferenceEngine.MakeGenericMethodFromArgTypes(genericMethod, t1, t2);
        Console.WriteLine("{0}, {1} => {2}", t1, t2, concreteMethod.ReturnType);

        //var nonGenericMethod = genericMethod.MakeGenericMethod(typeArgs.ToArray());

    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top