Type.GetInterfaces() for declared interfaces only

2019-06-22 09:26发布

First of all, there are many questions just like this, and perhaps some OPs were even asking this exact same question. The problem is that no answer to those questions (accepted or not) actually answer this question, at least none that I can find.

How can I determine the interfaces a class declares directly, and not those inherited either by parents nor by the interfaces declared?

e.g.

interface I {}
interface W : I {}
class C : W {}
class D : C, I {}
class E : D {}

Results:

  1. C declares W
  2. D declares I
  3. E declares none

An acceptable solution could require the interfaces to have at least one method.

If you think it truly is impossible, be careful not to make this mistake, which actually can be done.

The InterfaceMap handles many cases, but not all (I give an example below not solvable by InterfaceMap). One idea I had, but don't know how to implement it, is to decompile the bytecode of the class and see what is declared, as tools such as ILSpy correctly identify every case! If you like this idea, please give me a link to more info in this area.

I expect some of you will advise me to clean up my design. If this is not your argument, the rest of the post is not relevant for you.

Part of my project's purpose is in tracing potential code paths of given Types (at runtime). In order to determine programmatically which method will be called on a target type, without actually invoking the method nor creating an instance of the target type, knowing the declared interfaces of a target type is requisite to deterministically solve this. 'Nay' you say? Consider:

interface I { int Foo(); }
class C : I { public int Foo() { return 1; } }
class D : C { public new int Foo() { return 2; } }
class E : D, I { }

C p = new E();
Assert.AreEqual(1 or 2, (p as I).Foo())

The correct answer is 2, but if you change the declaration of E to not include I directly, the answer is 1. Now sure this is edge case, but it is the correct answer too. My engine is therefore is not fully compatible with potential user code. Telling the users to clean their code in order to use my tool is not acceptable. (Note there are dozens more interesting rules with casting to an interface, but I'll not discuss them here).

标签: c# reflection
5条回答
戒情不戒烟
2楼-- · 2019-06-22 10:05

The problem is not that GetInterfaces gives you wrong information, I think. The problem is that you’re looking at the wrong type.

When you have code like (p as I).Foo(), then you should be looking at the methods of typeof(I): not p.GetType(), nor typeof(C), nor typeof(E).

Reflecting on I will tell you everything you need to resolve the call correctly, though this is by no means trivial.

查看更多
仙女界的扛把子
3楼-- · 2019-06-22 10:07

Based on the helpful information from the comments, I was able to definitively show that this cannot be done with msft reflection (though it can with mono.cecil). The reason is that the GetInterfaces calls makes a native call which returns the interfaces pre-flattened for the target type.

查看更多
戒情不戒烟
4楼-- · 2019-06-22 10:14

What about this?

Type type = typeof(E);
var interfaces = type.GetInterfaces()
    .Where(i => type.GetInterfaceMap(i).TargetMethods.Any(m => m.DeclaringType == type))
    .ToList();
查看更多
forever°为你锁心
5楼-- · 2019-06-22 10:19

To get only the declared interfaces for a given type you could use GetInterfaces on the given type, then if it has a BaseType you could use the Except enumerator to exclude the base type's interfaces...

It's untested, but perhaps something like this extension method...

public static IEnumerable<Type> GetDeclaredInterfaces(this Type t)
{
    var allInterfaces = t.GetInterfaces();
    var baseInterfaces = Enumerable.Empty<Type>();
    if (t.BaseType != null)
    {
        baseInterfaces = t.BaseType.GetInterfaces();
    }
    return allInterfaces.Except(baseInterfaces);
}
查看更多
ゆ 、 Hurt°
6楼-- · 2019-06-22 10:21

In order to determine programmatically which method will be called on a target type, without actually invoking the method nor creating an instance of the target type, knowing the declared interfaces of a target type is requisite to deterministically solve this.

The Reflection API provides exactly this functionality via Type.GetInterfaceMap(Type). For example, in your case, typeof(E).GetInterfaceMap(typeof(I)) gives you the following information which allows you to determine the mapping directly:

InterfaceMethods            TargetMethods
[0]: I.Foo()                [0]: E.I.Foo()

Clearly, TargetMethods tells you exactly which method gets called if the instance is of type E at run-time.

You will notice that the interface method I.Foo() actually maps to a method on the type E that explicitly implements I.Foo(). This method was inserted by the compiler and it just calls D.Foo().

(Update: Oops, as you can see in the screenshot, I made the second Foo() virtual. I get exactly the same result for non-virtual though.)

查看更多
登录 后发表回答