Daniel Cazzulino's Blog

You might think this is a trivial thing, with Type.BaseType and Type.GetInterfaces already there. But there’s catch: the GetInterfaces method will give you all the implemented interfaces by the concrete type, as well as all its base types, as well as all the interfaces inherited by other interfaces it implements. What a mess! To make it more clear, say you have the following types:

If you look back to Derived definition, it doesn’t implement any interfaces itself, but rather it’s the base class. So, in order to have a more precise information about the type, we should be able to get this instead:

Derived
Base
Object
IBase
ICloneable

Now, this could be very useful in a few scenarios. My particular one involves finding an adapter that is registered for the closest type to the actual instance (i.e. if I have adapters registered for Derived, IBase and ICloneable, I want to be able to give them priority automatically based on where they appear in the precise type hierarchy).

So in order to do this, the first thing I needed was a structure that could represent a Type and the list of its implemented interface and base type, where in turn every member is also a tuple of the Type and the list of its own interfaces and base type and so on. A recursive generics structure like:

Tuple<Type, List<Tuple<Type, List<Tuple<Type, List<....

But of course that doesn’t work . As almost everything these days, I found the answer in StackOverflow: just give the type a name so you can reference it in the type itself:

I tend to forget just how useful the Aggregate Linq extension method is. Here you can see it action building the indenting string based on the indent level that is incremented on each recursion. I’m sure there are many other ways of doing that .

Ok, next, adding the interfaces, which is the interesting part of course . In our sample hierarchy, we have:

To add the interfaces we’ll use an often-ignored member in the reflection API: GetInterfaceMap. This method gives us the mapping between the interface members and the actual methods implementing them. So we’ll say that an interface should be added to a type inheritance list only if all its members have method mappings declared on the current type:

Ok, essentially, we need to implement the case where the type is not a Class . So we first need to get the IFormattable interface out of under the Base class. For that, we can just look at all the interfaces we get from Reflection (remember we’ll get also interfaces that are inherited by other interfaces we actually implement), and get all the interfaces, recursively, that we get up the hierarchy. With that list, we can quickly discard interfaces that seem "top-level" (i.e. IFormattable under Base) because it already shows up somewhere up the hierarchy of IBase. I have a helper Flatten method that does exactly what Matt Warren shows in the MSDN forums, and with that, I only need to add a Where to the AddRange:

Now we need add the case where type.IsClass is false (so we’re for example in IBase and we want IFormattable to be added). We just need to add the interfaces from the flattened list we built above, that do not show up elsewhere in the hierarchy, and that’s our final implementation:

public static TypeInheritance GetInheritanceTree(this Type type)
{
var list = new TypeInheritance(type);
// Gives us a map of Interface + All ancestor interfaces in the entire hierarchy up.
var interfaces = type
.GetInterfaces()
.Select(i => new { Interface = i, Ancestors = i.GetInterfaces().Flatten(n => n.GetInterfaces()) });
if (type.IsClass)
{
if (type.BaseType != null)
list.Item2.Add(GetInheritanceTree(type.BaseType));
list.Item2.AddRange(type
// Add all interfaces of the type, but
.GetInterfaces()
.Where(i => !interfaces.SelectMany(n => n.Ancestors).Any(t => t == i))
// See if the map gives us where the interface members are implemented
.Select(i => new { Interface = i, Map = type.GetInterfaceMap(i) })
// Either it is a marker interface, or all members are declared by the type.
// (explicit interface implementation or otherwise we are the first class in the hierarchy to introduce the interface).
.Where(i => i.Map.TargetMethods.All(m => m.DeclaringType == type))
.Select(i => GetInheritanceTree(i.Interface)));
}
else
{
// Then we only add those interfaces that do not show up as ancestors in any other
// interface in the list.
list.Item2.AddRange(interfaces
.Select(i => i.Interface)
.Where(i => !interfaces.SelectMany(n => n.Ancestors).Any(t => t == i))
.Select(i => GetInheritanceTree(i)));
}
return list;
}

Giving us the correct output:

Derived
Base
Object
IBase
IFormattable
ICloneable

Enjoy!

Posted bykzu

3 Comments

I think there’s a special case you have not handled but I could be wrong since I’ve never used the GetMethodMAp. I would however expect any marker interfaces (no methods at all) to show at the bottom level. Enumerable.All returns true for an empty sequence

runefs: yes, marker interfaces are tricky. I should probably add them at the top.

Hollander: overrides are tricky. In my case, I wanted to see where the interface was declared, not overriden. If you re-define the interface with “new” overloads, I think the right thing to do would be for it to appear, but “new” is (at least to me) generally a bad practice, and implementing interfaces without virtuals (i.e. explicitly implemented in the base) are also discouraged in .NET (i.e. fxcop/stylecop).