9

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

Community
  • 1
  • 1
payo
  • 4,501
  • 1
  • 24
  • 32
  • 2
    Your code sample at the bottom of the question makes no sense. What do you expect to happen when you write `new E()`? – David Heffernan Mar 20 '12 at 19:08
  • Thank you, and fixed, E should inherit D – payo Mar 20 '12 at 19:09
  • 1
    If it were truly redundant it would not change the outcome of the test. – payo Mar 20 '12 at 19:13
  • @ErikPhilips thanks, I editted the post to show a clear question at the start. – payo Mar 20 '12 at 19:17
  • 1
    One additional quesiton, when you stated `Unfortunately (part of) my project's purpose is in tracing code paths without having to run the code` does that mean you are not creating these objects and you're project is reading the raw text .cs file? – Erik Philips Mar 20 '12 at 19:25
  • The types are provided in a runtime environment, not from raw source files. – payo Mar 20 '12 at 19:26
  • I've edited the description slightly to try to remove that confusion – payo Mar 20 '12 at 19:28
  • 2
    You want to know what interfaces a class reimplements? See http://blogs.msdn.com/b/ericlippert/archive/2011/12/08/so-many-interfaces-part-two.aspx – Gabe Mar 20 '12 at 19:35
  • Not "reimplements", which interfaces it "declares". Note in the last example, E does not reimplement I, but declaring it affects the test. – payo Mar 20 '12 at 19:39
  • @Gabe I read the blog post, it discusses cases I also have considered for this problem, but he doesn't provide a solution to find those interfaces programmatically. But, upvote on your comment because these are edge cases I should keep in mind. – payo Mar 20 '12 at 19:43
  • Declaring `class E : I` tells the compiler that class `E` reimplements `I`. It will do the same thing if you move the declaration of `I` from `E` to `D`. – Gabe Mar 20 '12 at 20:42
  • It is true the test results in `2` when `D : I`, but when neither `D` nor `E` have `I`, there appears to be no solution to know this (or at least an unambiguous solution with all other cases (See the pastebin list of cases: pastebin.com/fuhzKRMw ) – payo Mar 20 '12 at 20:56

5 Answers5

4

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);
}
Reddog
  • 15,219
  • 3
  • 51
  • 63
  • 4
    Unfortunately, this does not work. Note that a derived type has the option to declare an interface again, this can affect which methods are called (see the ending example). – payo Mar 20 '12 at 19:30
4

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.

payo
  • 4,501
  • 1
  • 24
  • 32
1

What about this?

Type type = typeof(E);
var interfaces = type.GetInterfaces()
    .Where(i => type.GetInterfaceMap(i).TargetMethods.Any(m => m.DeclaringType == type))
    .ToList();
Balazs Tihanyi
  • 6,659
  • 5
  • 23
  • 24
  • I also have tried this, so I like your attempt. This does not work unless the derived type actually declares methods from the interface. In the last example (with `E` and `I`), `E` does not map to any methods from the interface. There are other cases InterfaceMap is not sufficient. I created a test case to help prove my attempts. See this pastebin: http://pastebin.com/fuhzKRMw – payo Mar 20 '12 at 19:37
  • 1
    @payo I've tried this on your example and it worked well. But I'm now going to test it on your other test cases. – Balazs Tihanyi Mar 20 '12 at 19:42
  • You are right, this does work on the example given, but not for all cases. I'm reviewing why this worked on the example tho, that is interesting. – payo Mar 20 '12 at 19:49
  • It seems when there is a non mapped parent, the child (also unmapped) loses accuracy. I am thinking some recursion of unmapped bases might be sufficient to answer the problem but a very similar variation. I'll test this more thoroughly. What is interesting to me is that I had looked at DeclaringType and found it to be insufficient. However, seeing it work in this case makes me relook at the failing cases. – payo Mar 20 '12 at 19:57
  • This doesn't pan out sufficiently. The problem degrades to ambuiguity and recursion is not a solution. I feel the dread of defeat. I looked into ILSpy, they simply use mono as mono as far more descriptive reflection metadata. I'll consider using the mono runtime as well. – payo Mar 20 '12 at 21:53
1

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

Timwi
  • 65,159
  • 33
  • 165
  • 230
  • Hmm, including .net symbol I see E in the stack. Looking more into this now. – payo Mar 20 '12 at 23:52
  • upvote for clarification, but was not the answer to the original question. – payo Mar 21 '12 at 01:34
  • nevermind, it is still ambiguous in some cases. That's too bad, I was hoping your added insight on InterfaceMap would be enough. Thanks for the help tho. – payo Mar 21 '12 at 02:30
  • 1
    I wanted to point out that this is not the answer to the question. Though you have shown that pathing can be predetermined in all cases, the question is how to list the declared interfaces. Pathing was only one reason for this purpose. What surprises me is the Mono.Cecil exposes this information. – payo Mar 21 '12 at 02:49
0

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.

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
  • GetInterfaces() gives the right information for another use case, yes. Unfortunately, typeof(I) does not tell me if E declares I directly. Test the code with and without E declaring it inherits I explicitly. – payo Mar 20 '12 at 19:15