2014-09-06 59 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; 
     } 
    } 
}