如何检查接口';s MethodInfo是一个“;新的“;方法

本文关键字:一个 MethodInfo 方法 新的 检查 接口 何检查 | 更新日期: 2023-09-27 18:20:48

我正在尝试使用反射来检索接口及其基本接口的所有方法的列表。

到目前为止,我有这个:

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:如果有帮助,您可以假设我可以访问派生接口的具体实例。该实例的类型只有在运行时才知道(它是一个动态代理)。

如何检查接口';s MethodInfo是一个“;新的“;方法

好吧,一种糟糕的方法可能是手动检查方法签名是否匹配。

检查签名的方法可能如下所示:

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)

因此,它保留了任何未隐藏的基本接口方法,任何派生接口方法(隐藏或其他),并接受任何签名更改(即,导致不隐藏的不同参数)。

EDITx3:添加了另一个带有过载的测试:

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

结果为:

MethodOverloadTest() for IInterfaceWithNewMethod
MethodOverloadTest(object) for IBaseInterface

我最终使用了Chris Sinclair和Thomas Levsque的混合答案。

它更广泛一些,但更健壮。

更重要的是,我认为它更容易阅读和推理,这是处理反思时的首要任务。我们都知道反射代码变得复杂和混乱是多么容易。。。

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