1

I am looking for an easy way to get the reflection information on the method starting all the way from the class, and going back all the way to the declaring interface. Here is the simplified piece of code:

public interface Ix
{
    void Function();
}
public class X : Ix
{
    public void Function() {}
}

public class Y : X
{
}

Method info for class X has no information about Function being declared in Ix interface. Here is the call:

var info = typeof(X).GetMethod("Function");
var baseInfo = info.GetBaseDefinition()

It returns the following data:

info.DeclaringType --> MyNamespace.X
info.ReflectedType --> MyNamespace.X
baseInfo.DeclaringType --> MyNamespace.X
baseInfo.ReflectedType --> MyNamespace.X

The same information is returned for class Y.

How can I figure out that this Function was declared in interface Ix without going through all implemented interfaces and base classes of class X or Y? I could be missing something simple but I cannot figure out what. Could this be a bug?

This is .Net Core SDK version 2.1.104 and Visual Studio 2017

Optional Option
  • 1,521
  • 13
  • 33
  • 2
    When you say "could this be a bug?" what exactly do you mean? Say which behaviour you expected, which behaviour you observed, and **the sentence of the published documentation which establishes that the observed behaviour is incorrect**. – Eric Lippert Apr 30 '18 at 18:30
  • Can you explain why you are calling `GetMethod` and not `GetInterfaceMap`? – Eric Lippert Apr 30 '18 at 18:32
  • 1
    This makes sense, an interface declaration is not a base definition. An interface is more of a mapping. The interface method Ix.Function is mapped to X.Function. That information is available via the the appropriately named GetInterfaceMap method. – Mike Zboray Apr 30 '18 at 18:32
  • I incorrectly assumed that GetBaseDefinition() could be used to walk all the way to the first declaration of the method. Using GetInterfaceMap seems like a very expensive way to find out the "history" of the method. It means that I would have to check each implemented interface all the way to the base class. Seems very inefficient. – Optional Option Apr 30 '18 at 19:19
  • Efficiency is value produced divided by resources consumed. What's the precious resource that you cannot spend? – Eric Lippert Apr 30 '18 at 19:37
  • Time and money. – Optional Option Apr 30 '18 at 20:33
  • So you're saying that it will cost you too many hours of developer time to write a correct program? What is the cost in developer time of writing an *incorrect* program? – Eric Lippert May 01 '18 at 15:52
  • Submit your question to StackOverflow. – Optional Option May 02 '18 at 03:41

2 Answers2

6

Here is an extension method to retrieve the Types of the interfaces that a particular MethodInfo implements in its declaring Type:

First, a helper method to get all the interface maps for a Type:

public static IEnumerable<InterfaceMapping> GetAllInterfaceMaps(this Type aType) =>
    aType.GetTypeInfo()
         .ImplementedInterfaces
         .Select(ii => aType.GetInterfaceMap(ii));

Then, an extension method that uses the helper to get the interfaces for a single method:

public static Type[] GetInterfacesForMethod(this MethodInfo mi) =>
    mi.ReflectedType
      .GetAllInterfaceMaps()
      .Where(im => im.TargetMethods.Any(tm => tm == mi))
      .Select(im => im.InterfaceType)
      .ToArray();

If you wanted the interfaces for all the methods of a class, you can use this instead:

public static ILookup<MethodInfo, Type> GetMethodsForInterfaces(this Type aType) =>
    aType.GetAllInterfaceMaps()
         .SelectMany(im => im.TargetMethods.Select(tm => new { im.TargetType, im.InterfaceType, tm }))
         .ToLookup(imtm => imtm.tm, imtm => imtm.InterfaceType);

To get the interface declarations that correspond to the method, you can use this:

public static IEnumerable<MethodInfo> GetInterfaceDeclarationsForMethod(this MethodInfo mi) =>
    mi.ReflectedType
      .GetAllInterfaceMaps()
      .SelectMany(map => Enumerable.Range(0, map.TargetMethods.Length)
                                   .Where(n => map.TargetMethods[n] == mi)
                                   .Select(n => map.InterfaceMethods[n]));
NetMage
  • 26,163
  • 3
  • 34
  • 55
  • Could be quite slow when querying all methods of the class but this is beautiful LINQ. It also shows the way to get to the interface quite nicely. Love this. – Optional Option Apr 30 '18 at 23:08
  • @OptionalOption For all methods it would make more sense to stop at the first `Select` and just go through each interfaces interface map. – NetMage Apr 30 '18 at 23:10
  • @OptionalOption I added an extension method to get all methods for interfaces. – NetMage Apr 30 '18 at 23:22
  • Here is the final query that works for me: mi.ReflectedType.GetTypeInfo() .ImplementedInterfaces .Select(ii => mi.ReflectedType.GetInterfaceMap(ii)) .Select(map => map.TargetMethods.Zip(map.InterfaceMethods, (t, i) => new { target = t, declaration = i})) .SelectMany(m => m) .Where(im => im.target == mi) .Select(im => im.declaration) – Optional Option Apr 30 '18 at 23:45
  • @OptionalOption Nice. I don't like using `Zip` or creating new objects if possible, so I added my own version (helps to think inside out sometimes) at the bottom of my answer. – NetMage May 01 '18 at 00:24
1

You seem to want a reverse lookup of the information that can be obtained from GetInterfaceMap. Building this is pretty straightforward,

public static Dictionary<MethodInfo, List<(Type, MethodInfo)>> GetReverseInterfaceMap(Type t)
{
    var reverseMap = new Dictionary<MethodInfo, List<(Type, MethodInfo)>>();
    var maps = t.GetInterfaces().ToDictionary(i => i, i => t.GetInterfaceMap(i));
    foreach (var m in t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
    {
        var list = new List<(Type, MethodInfo)>();
        foreach (var (i, map) in maps)
        {
            for (int index = 0; index < map.TargetMethods.Length; index++)
            {
                var targetMethod = map.TargetMethods[index];
                if (targetMethod == m)
                {
                    list.Add((map.InterfaceType, map.InterfaceMethods[index]));
                    break;
                }
            }
        }

        reverseMap[m] = list;
    }

    return reverseMap;
}

There are a few points to be made about how interfaces work because the picture is not as simple as going Derived -> Base -> Interface. There is a mapping which describes the concrete method each interface method calls. This information is readily available because the runtime needs it to implement interface dispatch. However the reverse is not as simple. One concrete method might be a mapping for multiple interfaces. Also it is possible for interface methods to be remapped by derived classes. This all needs to be taken into account.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122