2014-09-06 4 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.

Fiddle: https://dotnetfiddle.net/fwVeLS

PS: Если это поможет, то можно предположить, у меня есть доступ к конкретному экземпляру производного интерфейса. Тип этого экземпляра будет известен только во время выполнения (это динамический прокси).

+0

Не понимаю, без нового ключевого слова также он будет вести себя одинаково правильно? AFAIK нет никакой разницы в применении ключевого слова 'new'. Я что-то упускаю? Какова конечная цель? –

+0

@SriramSakthivel Да, «новое» ключевое слово является необязательным. Важно то, что один метод затеняет другой. Я работаю над [этой проблемой] (https://github.com/AutoFixture/AutoFixture/issues/306) для AutoFixture, и мне нужно иметь возможность использовать отражение для настройки всех методов макета интерфейса + его базы интерфейсы. Однако, если метод тени другого, я должен * не * настроить теневой метод. – dcastro

ответ

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))); 

Примечание: это немного наивная реализация. Я не удивлюсь, если есть более простой способ или угловой случай, который это не охватывает.

EDIT: Немного неправильно понял желаемый результат. Используя код выше, это даст вам все методы базового интерфейса, плюс полученные методы интерфейса, но отфильтрованы любые методы базового интерфейса, которые были скрыты в производном интерфейсе:

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') недостаточно. Вы также должны проверить, является ли это «вне» ('p.IsOut') или параметр« ref »(' p.ParameterType.IsByRef'). – dcastro

+0

@ dcastro: Ах, конечно. Я был уверен, что что-то забыл. : P Рад, что это сработало для вас! –

+0

Я закончил тем, что использовал свой подход, который представляет собой смесь твоей и Томаса. Я отправил его в качестве ответа, но я сохраню ваш в качестве принятого ответа :) Cheers – dcastro

1

я в конечном итоге, используя комбинацию Криса Синклер и ответы Томаса Lévesque в.

Это немного более обширный, но он более надежный.

Что еще более важно, я думаю, что читать и рассуждать гораздо проще, что является главным приоритетом при рассмотрении рефлексии. Мы все знаем, как легко для кода отражения стать сложным и целым беспорядком ...

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; 
     } 
    } 
} 
Смежные вопросы