如何检查界面的MethodInfo是否是" new"方法

时间:2014-09-06 13:33:40

标签: c# .net reflection

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

到目前为止,我有这个:

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

我希望能够过滤掉在基础接口中声明的 shadow 方法的方法,即" new"方法:

public interface IBaseInterface
{
    string Method();
}

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

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

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

PS:如果有帮助,您可以假设我可以访问派生界面的具体实例。该实例的类型只能在运行时知道(它是一个动态代理)。

2 个答案:

答案 0 :(得分: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)

因此它保留了任何不被隐藏的基本接口方法,任何派生的接口方法(隐藏或其他方式),并且尊重任何签名更改(即不会隐藏的不同参数)。 / p>

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 :(得分:1)

我最终使用了Chris Sinclair和Thomas Levesque的答案。

它有点广泛,但它更强大。

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

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