从参数动态推断泛型方法的类型参数

时间:2014-03-27 15:07:17

标签: c# generics reflection types inference

函数MethodInfo.MakeGenericMethod要求您传递与类型参数对应的类型数组。我处于一种场景,其中类型签名非常重要,直到运行时才知道。我只有functino的参数类型。

例如,这里是一个函数签名:

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

我有两个类型为t1的参数(例如IArray)和t2(例如Func),仅在运行时才知道。我想在运行时利用C#类型推理算法,例如从t1和t2计算T和U.

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

1 个答案:

答案 0 :(得分:0)

现在我有一个手动解决方案,可以满足我的需求,但感觉必须有更好的方法:

    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());

    }