2014-09-06 61 views
6

我试图使用反射检索接口+其基接口的所有方法的列表。如何检查接口的MethodInfo是否为“新”方法

到目前为止,我有这样的:

var methods = type.GetMethods().Concat(
       type.GetInterfaces() 
        .SelectMany(@interface => @interface.GetMethods())); 

我希望能够过滤掉影子基本接口,即宣布方式方法,“新”的方法:

public interface IBaseInterface 
{ 
    string Method(); 
} 

public interface IInterfaceWithNewMethod : IBaseInterface 
{ 
    new string Method(); 
} 

使用我当前的代码,结果包括两种方法 - 我只想检索IInterfaceWithMethod.Method并过滤出IBaseInterface.Method

小提琴:https://dotnetfiddle.net/fwVeLS

PS:如果有帮助,你可以假设我有机会获得派生接口的具体实例。只有在运行时才知道该实例的类型(它是一个动态代理)。

+0

我不明白,没有新的关键字也,它会表现得同样的权利? AFAIK在应用'new'关键字方面没有任何区别。我错过了什么吗?什么是最终目标? – 2014-09-06 13:55:02

+0

@SriramSakthivel是的,“新”关键字是可选的。重要的是一种方法会影响另一种方法。我正在研究AutoFixture的[这个问题](https://github.com/AutoFixture/AutoFixture/issues/306),我需要能够使用反射来设置模拟接口的所有方法及其基础接口。但是,如果一个方法影响另一个,我必须*不*设置阴影方法。 – dcastro 2014-09-06 13:58:23

回答

2

那么,一个肮脏的方式可能是手动检查方法签名是否匹配。

检查签名可能是这样一种方法:

public static bool HasSameSignature(MethodInfo potentiallyHidingMethod, MethodInfo baseMethod) 
{ 
    //different name, therefore not same signature 
    if (potentiallyHidingMethod.Name != baseMethod.Name) 
     return false; 

    //now we check if they have the same parameter types... 
    var potentiallyHidingMethodParameters = potentiallyHidingMethod.GetParameters(); 
    var baseMethodParameters = baseMethod.GetParameters(); 

    //different number of parameters, therefore not same signature 
    if (potentiallyHidingMethodParameters.Length != baseMethodParameters.Length) 
     return false; 

    for (int i = 0; i < potentiallyHidingMethodParameters.Length; i++) 
    { 
     //if a parameter type doesn't match, it's not the same signature 
     if (potentiallyHidingMethodParameters[i].ParameterType != baseMethodParameters[i].ParameterType) 
      return false; 
    } 

    //if we've gotten this far, they have the same name and parameters, 
    //therefore, it's the same signature. 
    return true; 
} 

然后,它的检查派生的接口方法,看看他们是否隐藏(或相匹配的签名)的事项的任何的底座接口方法:

Type type = typeof(IInterfaceWithNewMethod); 

var potentiallyHidingMethods = type.GetMethods(); 

var baseTypeMethods =type.GetInterfaces() 
       .SelectMany(@interface => @interface.GetMethods()); 

var hidingMethods = potentiallyHidingMethods 
    .Where(hiding => baseTypeMethods.Any(baseMethod => HasSameSignature(hiding, baseMethod))); 

请注意,这是一个天真的实现。如果有一个更简单的方法或角落案例,我不会感到惊讶。

编辑:稍微误解了所需的输出。使用上面的代码,这会给你所有的基本接口的方法,再加上衍生的接口中的方法,但筛选出来,由派生的接口隐藏任何基接口方法:

var allMethodsButFavouringHiding = potentiallyHidingMethods.Concat(
     baseTypeMethods.Where(baseMethod => !potentiallyHidingMethods.Any(potentiallyhiding => HasSameSignature(potentiallyhiding, baseMethod)))); 

EDITx2:我没有给出一个测试以下接口:

public interface IBaseInterface 
{ 
    string BaseMethodTokeep(); 

    string MethodToHide(); 
    string MethodSameName(); 
} 

public interface IInterfaceWithNewMethod : IBaseInterface 
{ 
    new string MethodToHide(); 
    new string MethodSameName(object butDifferentParameters); 

    string DerivedMethodToKeep(); 
} 

这导致与MethodInfo集合:

MethodToHide (IInterfaceWithNewMethod) 
MethodSameName (IInterfaceWithNewMethod) 
DerivedMethodToKeep (IInterfaceWithNewMethod) 
BaseMethodTokeep (IBaseInterface) 
MethodSameName (IBaseInterface) 

所以它使任何基本接口实现方法具没有隐藏的ds,任何派生的接口方法(隐藏或其他方式),并且尊重任何签名更改(即不会导致不隐藏的不同参数)。

EDITx3:增加了另外一个测试用的重载:

public interface IBaseInterface 
{ 
    string MethodOverloadTest(); 
    string MethodOverloadTest(object withParam); 
} 

public interface IInterfaceWithNewMethod : IBaseInterface 
{ 
    new string MethodOverloadTest(); 
} 

有了结果:

MethodOverloadTest() for IInterfaceWithNewMethod 
MethodOverloadTest(object) for IBaseInterface 
+1

谢谢,这似乎符合我的要求:D只需要注意一点:在你的'HasSameSignature'方法中,为了检查两个参数是否相同,检查它的类型('p.ParameterType')是不够的。你还应该检查它是否“out”('p.IsOut')或者“ref”参数('p.ParameterType.IsByRef')。 – dcastro 2014-09-06 14:26:11

+0

@dcastro:啊,当然。我确信我忘记了一些东西。 :P很高兴为你效力! – 2014-09-06 14:58:39

+0

我最终使用了我自己的方法,这是你和托马斯的混合。我已经发布了它作为答案,但我会保持你的接受答案:)干杯 – dcastro 2014-09-08 12:04:05

1

最后我用克里斯·辛克莱和托马斯·莱维斯克的答案的混合。

这是一个更广泛的,但它更强大。

更重要的是,我认为阅读和推理起来要容易得多,这在处理反思时是头等大事。我们都知道它是多么容易反射代码变得复杂和全乱了......

internal static class TypeExtensions 
{ 
    /// <summary> 
    /// Gets a collection of all methods declared by the interface <paramref name="type"/> or any of its base interfaces. 
    /// </summary> 
    /// <param name="type">An interface type.</param> 
    /// <returns>A collection of all methods declared by the interface <paramref name="type"/> or any of its base interfaces.</returns> 
    public static IEnumerable<MethodInfo> GetInterfaceMethods(this Type type) 
    { 
     var allMethods = type.GetMethods().Concat(
      type.GetInterfaces() 
       .SelectMany(@interface => @interface.GetMethods())); 

     return allMethods.GroupBy(method => new Signature(method)) 
         .Select(SignatureWithTheMostDerivedDeclaringType); 
    } 

    private static MethodInfo SignatureWithTheMostDerivedDeclaringType(IGrouping<Signature, MethodInfo> group) 
    { 
     return group.Aggregate(
      (a, b) => a.DeclaringType.IsAssignableFrom(b.DeclaringType) ? b : a); 
    } 

    private sealed class Signature 
    { 
     private readonly MethodInfo method; 

     public Signature(MethodInfo method) 
     { 
      this.method = method; 
     } 

     public override bool Equals(object obj) 
     { 
      var that = obj as Signature; 

      if (that == null) 
       return false; 

      //different names, therefore different signatures. 
      if (this.method.Name != that.method.Name) 
       return false; 

      var thisParams = this.method.GetParameters(); 
      var thatParams = that.method.GetParameters(); 

      //different number of parameters, therefore different signatures 
      if (thisParams.Length != thatParams.Length) 
       return false; 

      //different paramaters, therefore different signatures 
      for (int i = 0; i < thisParams.Length; i++) 
       if (!AreParamsEqual(thisParams[i], thatParams[i])) 
        return false; 

      return true; 
     } 

     /// <summary> 
     /// Two parameters are equal if they have the same type and 
     /// they're either both "out" parameters or "non-out" parameters. 
     /// </summary> 
     private bool AreParamsEqual(ParameterInfo x, ParameterInfo y) 
     { 
      return x.ParameterType == y.ParameterType && 
        x.IsOut == y.IsOut; 
     } 

     public override int GetHashCode() 
     { 
      int hash = 37; 
      hash = hash*23 + method.Name.GetHashCode(); 

      foreach (var p in method.GetParameters()) 
      { 
       hash = hash*23 + p.ParameterType.GetHashCode(); 
       hash = hash*23 + p.IsOut.GetHashCode(); 
      } 
      return hash; 
     } 
    } 
}