从参数动态推断泛型方法的类型参数
本文关键字:泛型方法 类型参数 参数 动态 | 更新日期: 2023-09-27 17:53:57
函数MethodInfo.MakeGenericMethod
要求您传递与类型参数对应的类型数组。我在一个场景中,类型签名是非平凡的,直到运行时才知道。我只知道函数的参数类型。
例如这里有一个函数签名:
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);
}
现在我有一个手动的解决方法,它可以很好地满足我的需求,但感觉必须有一个更好的方法:
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());
}