2017-10-11 25 views
3

出于测试目的,我正在检查引用类中的一系列方法签名是否已在不同的静态类上实现。对于大多数人以下工作:通用方法的Type.GetMethod()

private static IEnumerable<Signature> GetMethodSigs(Type type) 
{ 
    // Get MethodInfos, filter and project into signatures 
    var methods = type.GetMethods(
          BindingFlags.Public 
         | BindingFlags.DeclaredOnly 
         | BindingFlags.Static 
         | BindingFlags.Instance) 
        .Where(mi => !mi.Name.StartsWith("get_")) 
        .Where(mi => !mi.Name.StartsWith("set_")) 
        .Select(o => new Signature(o.Name, o.ReturnType, o.GetParameters().Select(pi => pi.ParameterType))); 

    return methods; 
} 

private static MethodInfo FindMethod(Type type, Signature sig) 
{ 
    MethodInfo member = type.GetMethod(
           sig.Name, 
           BindingFlags.Public | BindingFlags.Static, 
           null, 
           sig.ParameterTypes.ToArray(), 
           null); 
    return member; 
} 

public struct Signature 
{ 
    public string Name; 
    public Type ReturnType; 
    public IEnumerable<Type> ParameterTypes; 

    public Signature(string name, Type returnType, IEnumerable<Type> parameterTypes = null) 
    { 
     Name = name; 
     ReturnType = returnType; 
     ParameterTypes = parameterTypes; 
    } 
} 

这是一个测试类的一部分,但是想象一下下面的代码在驾驶过程:

foreach(var sig in GetMethodSigs(typeof(ReferenceClass))) 
    Console.WriteLine(FindMethod(typeof(TestClass), sig)?.ToString()); 

以下方法签名被拾起好上ReferenceClass,但FindMethod()不上TestClass找到一个等效的方法(这确实存在!):

public static void SomeMethod<T>(SomeDelegate<T> del) 

GetMethods()为我提供了一个del参数类型(SomeDelegate`1),但这显然不适合在GetMethod()中搜索,因为FindMethod()返回null此输入。

任何想法如何操纵从GetMethods()返回的值来搜索使用GetMethod()(泛型参数在代表中使用,而不是简单的参数类型)的泛型方法?

(我知道有一种GetMethod()版本只是走一个名字,但一些方法名的重载,我需要搜索参数类型为好。)

+0

可能是这样的帮助:https://stackoverflow.com/questions/232535/how-do-i-use-reflection-to-call-a-generic-method –

+0

@EhsanSajjad不幸的是,这些问题的答案(和其他类似的问题)假设该方法可以通过使用名称找到 - 我需要包含参数,这就是我遇到麻烦的地方。 –

+0

与其使用类型参数调用'GetMethod',为什么不调用'GetMethods'然后遍历结果(匹配名称)并比较参数类型? – BurnsBA

回答

1

相反的Type.GetMethod()重载之一,我结束了对目标类使用GetMethods()和使用扩展方法的每个参数类型比较通过所有成员循环(注意C#7仅本地函数):

public static bool Similar(this Type reference, Type type) 
{ 
    if (reference.IsGenericParameter && type.IsGenericParameter) 
    { 
     return reference.GenericParameterPosition == type.GenericParameterPosition; 
    } 

    return ComparableType(reference) == ComparableType(type); 

    Type ComparableType(Type cType) 
     => cType.IsGenericType ? cType.GetGenericTypeDefinition() : cType; 
} 

这考虑两种类型是“相似”,如果:

  • 他们是简单类型和使用==操作
  • 比较相等
  • 它们是泛型类型,并且在通用参数列表中具有相同索引的类型(即,在SomeMethod<T,S>(S parameter)中,唯一的参数类型将被认为与SomeMethod<T1,T2>(T2 parm)中的相同,但是不是SomeMethod<T,S>(T parameter))。
  • 它们是具有嵌套泛型参数的相同类型。在这种情况下,事实上,它是一个通用型的注意,但没有关于参数的进一步探讨(等SomeMethod<T,S>(Action<T> parameter)参数类型将是“相似” SomeMethod<T,S>(Action<S> parameter)

这是不理想,但事实证明这是一个令人惊讶的难题!它适用于我的使用案例,它覆盖了我的所有案例,并且由于项目的性质(分析遗留代码),不会出现新案例。在Type的下列扩展中,它旨在取代Type.GetMethod()并实现上面提到的循环:

public static MethodInfo GetMethodWithGenerics(
          this Type type, 
          string name, Type[] parameters, 
          BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) 
{ 
    var methods = type.GetMethods(flags); 

    foreach (var method in methods) 
    { 
     var parmeterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); 

     if (method.Name == name && parmeterTypes.Count() == parameters.Length) 
     { 
      bool match = true; 

      for (int i = 0; i < parameters.Length; i++) 
       match &= parmeterTypes[i].Similar(parameters[i]); 

      if (match) 
       return method; 
     } 
    } 

    return null; 
} 

正如BurnsBA在他的回答下面所说,似乎有一些基本问题与泛型的内置反射支持,似乎并没有一个简单的解决方案,我的原始问题。在考虑了BurnBA在这里的回答以及在另一个问题上的he linked to之后,我得出了这个答案。对于任何希望产生比较完整版本的人来说,这个答案都会特别有用。

任何发现这个有用的人都应该考虑upvoting或者这两者。

0

愿你可以尝试类似

string name = "MyFunction"; 
Type type = typeof(Program); 

MethodInfo member = type.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance) 
.Single(m => m.Name == name && m.GetParameters().Select(p => p.ParameterType.Name).SequenceEqual(new [] { typeof(MyClass<>).Name })); 

public void MyFunction<T>(MyClass<T> test){} 
1

我没有很长一段时间,你是比较两个不同的类定义注意:

foreach(var sig in GetMethodSigs(typeof(ReferenceClass))) 
    Console.WriteLine(FindMethod(typeof(TestClass), sig)?.ToString()); 

这是值得注意的,因为我如果类是相同的,你的上面的代码就可以工作。问题是泛型的处理方式(简要看Type源代码,看起来Equals使用引用比较,但==已卸载到我认为的编译器中。问题是,不同的泛型方法类型不是引用等效的。实际发生的事情是作为练习留给读者的)。

一个快速演示(你可以在交互式shell中运行这个)

public class D { public static void Method<T>(Action<T> t) { } } 
(new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType == (typeof(Action<>)) 

输出:

false 

但是,如果检查的类型,看起来都一样:

> (new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType.ToString() 
"System.Action`1[T]" 
> (typeof(Action<>)).ToString() 
"System.Action`1[T]" 

好吧,这是问题的演示。您应该能够解决此问题由GetMethodSigs改变你的SELECT语句来调用GetGenericTypeDefinition()每个参数类型:

.Select(o => new Signature(o.Name, o.ReturnType, o.GetParameters().Select(pi => pi.ParameterType.GetGenericTypeDefinition()))); 

你可以看到,现在下面显示的平等:

> (new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType.GetGenericTypeDefinition() == (typeof(Action<>)).GetGenericTypeDefinition() 
true 

当然,这仅适用于泛型类型。您必须添加一些逻辑,仅在上述选择语句中根据需要调用.GetGenericTypeDefinition()

更多来自https://stackoverflow.com/a/1855248/1462295


编辑:

使用从https://stackoverflow.com/a/7182379/1462295

代码(注意空类T从上方)

public class D 
{ 
    public static void Method<TT>(Action<TT> t) { } 
    public static void Method<TT>(TT t) { } 
} 

public class RefD 
{ 
    public static void Method<TT>(Action<TT> t) { } 
    public static void Method<TT>(TT t) { } 
} 

foreach(var sig in GetMethodSigs(typeof(RefD))) 
    Console.WriteLine(GetMethodExt(typeof(D), sig.Name, sig.ParameterTypes.ToArray())?.ToString()); 

输出

Void Method[TT](System.Action`1[TT]) 
Void Method[TT](TT) 
+0

根据Type.GetGenericTypeDefinition()的MSDN页面,Type.IsGenericType指出调用这个方法是否合适。 –

+0

我可能有点仓促接受这个答案。虽然使用'typeof(Action <>)'演示现在显示eqaulity,但'GetMethod()'显然仍然无法与使用'GetGenericTypeDefinition()'创建的提供的参数相匹配。 (续) –

+0

为了解决这个问题,我试着创建一个使用'GetMethods()'的新GetMethod()'扩展,然后遍历返回的方法寻找名称和参数上的匹配 - 我也可以使用'GetGenericTypeDefinition )'这些也是。这部分工作,但仍然不正确地匹配正常的泛型(形式为'void SomeMethod (T parameter)')。我想知道是否将实例方法与通用方法进行比较。 –