Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check whether an interface's MethodInfo is a "new" method

I'm trying to use reflection to retrieve a list of all methods of an interface + its base interfaces.

So far I have this:

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

I'd like to be able to filter out methods that shadow methods declared in base interfaces, i.e., "new" methods:

public interface IBaseInterface
{
    string Method();
}

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

With my current code, the result includes both methods - I'd like to retrieve IInterfaceWithMethod.Method only and filter out IBaseInterface.Method.

Fiddle: https://dotnetfiddle.net/fwVeLS

PS: If it helps, you can assume I have access to a concrete instance of the derived interface. The type of that instance will only be known at runtime (it's a dynamic proxy).

like image 811
dcastro Avatar asked Sep 06 '14 13:09

dcastro


2 Answers

Well, a dirty way might be to manually check if the method signatures match.

A method to check signatures might look like this:

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

Then it's a matter of checking the derived interface methods to see if they hide (or match the signature of) any of the base interface methods:

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

Note, this is a bit of a naive implementation. I wouldn't be surprised if there's a simpler way or corner cases that this doesn't cover.

EDIT: Slightly misunderstood the desired output. Using the code above, this will give you all the base interface methods, plus the derived interface methods, but filtered out any base interface methods that were hidden by the derived interface:

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

EDITx2: I did a test given the following interfaces:

public interface IBaseInterface
{
    string BaseMethodTokeep();

    string MethodToHide();
    string MethodSameName();
}

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

    string DerivedMethodToKeep();
}

This results with a collection of MethodInfo:

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

So it keeps any base interface methods that aren't hidden, any derived interface methods (that are hiding or otherwise), and honours any signature changes (that is, different parameters which would result in not hiding).

EDITx3: Added another test with overloads:

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

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

With results of:

MethodOverloadTest() for IInterfaceWithNewMethod
MethodOverloadTest(object) for IBaseInterface
like image 144
Chris Sinclair Avatar answered Nov 18 '22 15:11

Chris Sinclair


I ended up using a mix of Chris Sinclair and Thomas Levesque's answers.

It's a bit more extensive, but it's more robust.

More importantly, I think it's much easier to read and reason about, which is a top priority when dealing with reflection. We all know how easy it is for reflection code to become complex and a whole mess...

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;
        }
    }
}
like image 20
dcastro Avatar answered Nov 18 '22 15:11

dcastro