【问题标题】:How to perform method overload resolution for types known at runtime?如何为运行时已知的类型执行方法重载解析?
【发布时间】:2015-02-07 16:22:53
【问题描述】:

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

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&lt;int&gt;))我想动态选择最合适的方法。

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

  • Binder.SelectMethod() :该方法不支持泛型。
  • typeof().IsAssignableFrom() :同上。对泛型参数有一些支持(例如:提供泛型参数T 并且它对int 类型的约束将被正确评估)但不支持带有参数的泛型类型(例如:List&lt;int&gt; vs List&lt;&gt; 或 vs @987654331 @(此类型来自泛型方法,可能T 包含一些约束)

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

我知道这是一个复杂的话题,不是小问题,但有一些问题已经解决,应该会让事情变得更容易:

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

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

【问题讨论】:

    标签: c# generics reflection runtime overloading


    【解决方案1】:

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

    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

    【讨论】:

    • 感谢您的回答。无论如何,签名 ExecuteCase&lt;T1, T2&gt; 方法在 IMO 中并不好,因为它使用通用参数,这意味着我知道输入类型是运行时(我不认为这是 OP 的全部要点)。另外:例如,如果输入类型是 List&lt;int&gt;, int 并且匹配的方法是 Foo&lt;T&gt;(List&lt;T&gt; a, T b) ,那么 MakeGenericMethod try and catch 技术是否有效? (那么通用参数应该只是int 而不是List&lt;int&gt; 和int)。
    • 查看编辑。修复了您在上面评论中提到的案例的问题。还将ExecuteCase 从泛型更改为object。
    • 我使用了一个自定义解决方案,该解决方案的实现与微软的IsAssignableFrom() 方法非常相似:referencesource.microsoft.com/#mscorlib/system/… 不同之处在于它不是直接检查类型是否相等(例如:this == c)它使用了自定义方法它照顾泛型(例如:typeof(List&lt;int&gt; == typeof(List&lt;&gt;) 将是真的)它工作得很好。如果我有时间,我会发布该解决方案(清洁后)。
    猜你喜欢
    • 2016-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多