如何对运行时已知的类型执行方法重载解析?

时间:2015-02-07 16:22:54

标签: c# generics reflection runtime overloading

我有一个包含方法重载的类:

public static void Foo(string a, string b)
public static void Foo(DateTime a, DateTime b)
public static void Foo<T>(ICollection<T> a, T b)
public static void Foo<T>(T a, ICollection<T> b)
public static void Foo<T>(ICollection<T> a, ICollection<T> b)
public static void Foo<T, U>(T a, U b) where T : IComparable
public static void Foo<T, U>(T a, U b)

我在运行时以这种方式检索所有方法:

var methods = typeof(MyClass).GetMethods(BindingFlags.Public | BindingFlags.Static);

对于两个给定的参数类型(例如:typeof(int)typeof(List<int>)),我想动态选择最合适的方法。

到目前为止我尝试/检查的内容:

  • Binder.SelectMethod():该方法不支持泛型。
  • typeof().IsAssignableFrom():与上述相同。对通用参数有一些支持(例如:提供通用参数T并且它对类型int的约束将被正确评估)但不包含带参数的泛型类型(例如:List<int> vs { {1}}或vs List<>(此类型来自通用方法,可能List<T>包含一些约束))

  • 使用所有泛型方法的输入参数调用T,并捕获参数异常,直到没有:丑陋。 它可能还需要做一些额外的工作,因为我提供给MakeGenericMethod()的类型可能与我收到的初始类型不完全相同(例如:我有MakeGenericMethod()而我应该提供{{1} {}} {}

我知道这是一个复杂的主题,并不是一个小问题,但有一些问题已修复,应该让事情变得更容易:

  • 所有方法都有两个参数和相同的返回类型
  • 没有可选类型,没有数组。
  • 泛型方法不再有一个或两个通用参数,参数总是以相同的顺序排列。
  • 不需要支持协方差/逆变
  • 方法已按“优先级”排序。这就是为什么没有约束的通用的那个在这个列表的末尾。 一旦一种方法匹配,我们就可以接受并忽略其他方法

我想知道是否有办法使用C#Framework中的现有类/方法来解决这些重载(无需重新发明轮子),如果不可能,那么可能的解决方案是什么(自定义代码) )

1 个答案:

答案 0 :(得分:0)

考虑到您描述的先决条件,您可以这样做:

static void ExecuteCase(object a, object b)
{
    var method = GetMethod(typeof(Bar).GetMethods(BindingFlags.Public | BindingFlags.Static), a.GetType(), b.GetType());
    if (method != null) { method.Invoke(null, new object[] {a, b}); }
    else { /* Handle method not found */ }
}

GetMethod的位置:

private static MethodInfo GetMethod(IEnumerable<MethodInfo> methods, Type typeA, Type typeB)
{
    foreach (var method in methods)
    {
        List<MethodInfo> candidates = new List<MethodInfo>();
        if (!method.IsGenericMethod) { candidates.Add(method); }
        else
        {
            MethodInfo genericMethod;
            if (IsParameterOfGeneric(typeA, typeB) && TryMakeGenericMethod(method, typeB, typeB, out genericMethod)) candidates.Add(genericMethod);
            if (IsParameterOfGeneric(typeB, typeA) && TryMakeGenericMethod(method, typeA, typeA, out genericMethod)) candidates.Add(genericMethod);
            if (typeA.IsGenericType && typeB.IsGenericType && TryMakeGenericMethod(method, typeA.GetGenericArguments()[0], typeB.GetGenericArguments()[0], out genericMethod)) candidates.Add(genericMethod);
            if (TryMakeGenericMethod(method, typeA, typeB, out genericMethod)) candidates.Add(genericMethod);
        }

        foreach (var candidate in candidates)
        {
            var args = candidate.GetParameters();
            if (args[0].ParameterType.IsAssignableFrom(typeA) && args[1].ParameterType.IsAssignableFrom(typeB)) return candidate;
        }
    }

    return null;
}

private static bool IsParameterOfGeneric(Type generic, Type parameter)
{
    if (!generic.IsGenericType) return false;
    return generic.GetGenericArguments()[0] == parameter;
}

private static bool TryMakeGenericMethod(MethodInfo method, Type typeA, Type typeB, out MethodInfo genericMethod)
{
    genericMethod = null;
    try
    {
        genericMethod = method.GetGenericArguments().Length == 1
            ? method.MakeGenericMethod(typeA)
            : method.MakeGenericMethod(typeA, typeB);

        return true;
    }
    catch (ArgumentException ex)
    {
        if (ex.Message.Contains("violates the constraint of type")) return false;
        throw;
    }
}

仅供参考,why use try-catch on TryMakeGenericMethod