views:

99

answers:

6

Updated question given Andrew Hare's correct answer:

Given the following C# classes:

public class Bar : Foo, IDisposable
{
    // implementation of Bar and IDisposable
}

public class Foo : IEnumerable<int>
{
    // implementation of Foo and all its inherited interfaces
}

I want a method like the following that doesn't fail on the assertions (Note: you cannot change the assertions):

public void SomeMethod()
{
   // This doesn't work
   Type[] interfaces = typeof(Bar).GetInterfaces();

   Debug.Assert(interfaces != null);
   Debug.Assert(interfaces.Length == 1);
   Debug.Assert(interfaces[0] == typeof(IDisposable));
}

Can someone help by fixing this method so the assertions don't fail?

Calling typeof(Bar).GetInterfaces() doesn't work because it returns the entire interface hierarchy (i.e. interfaces variable contains IEnumerable<int>, IEnumerable, and IDisposable), not just the top level.

+2  A: 

There really isn't any way to do this since you are retrieving all interfaces from the interface hierarchy. This means that when you implement IEnumerable<T> you are also implicitly implementing IEnumerable as well.

In other words, if you look at the IL for the class you have created you will see this:

.class public auto ansi beforefieldinit Foo
        extends [mscorlib]System.Object
        implements [mscorlib]System.Collections.Generic.IEnumerable`1<int32>, 
                   [mscorlib]System.Collections.IEnumerable
{
    // ... 
}

Even though you only indicated that your type implements IEnumerable<T>, the compiler has emitted IL that indicates your type implements IEnumerable<T> and IEnumerable.

The reflection API is happily returning what you have actually defined on the type (which is that your type implements both interfaces - which it actually does). The C# compiler allows you to only reference the bottommost type in the interface hierarchy as it will fill in the other interfaces that your type also implements. This is one of the ways that interface inheritance differs from type inheritance.

Andrew Hare
Andrew - your answer was correct; my question just wasn't as complete as it needed to be. Thanks!
Jordan
Actually you can filter these by looking at the BaseType of the interface type. See my answer elsewhere.
Morten Mertner
A: 

I would write it as:

public void SomeMethod()
{
   Type[] interfaces = typeof(Foo).GetInterfaces();
   Debug.Assert(interfaces.Contains(typeof(IEnumerable<int>)));
}

But it's hard to answer without knowing what you are trying to test. Regardless, you should not rely on the order when using GetInterfaces and that method will return an empty array if the type doesn't implement any, so the null check is not needed.

Edit: if you really can't change the assertions, then the safe thing to do is:

        Type[] allInterfaces = typeof(Foo).GetInterfaces();
        var interfaces = allInterfaces.Where(x => x == typeof(IEnumerable<int>)).ToArray();

        Debug.Assert(interfaces != null);
        Debug.Assert(interfaces.Length == 1);
        Debug.Assert(interfaces[0] == typeof(IEnumerable<int>));
Jamie Ide
How would you write it without changing the assertions?
Jordan
I wouldn't. The first assertion (null check) will always pass (though it's not harmful), the second only has meaning if you only implement interfaces under your control, and the third (access by index) is specifically warned against in the documentation.
Jamie Ide
@Jamie, true, but he specifically used those assertions only for his original Foo type, which explicitly implemented only a single interface. Your "safe code" is a tautology. It sort of sidesteps the entire question.
Cheeso
+2  A: 

Andrew Hare is correct that you cannot retrieve the specified list of interfaces using reflection. However you can find the "top-level" interfaces by excluding any interfaces that are implied by others. You could implement it like this:

Type[] allInterfaces = typeof(Foo).GetInterfaces();
Type[] interfaces = allInterfaces
   .Where(x => !allInterfaces.Any(y => y.GetInterfaces().Contains(x)))
   .ToArray();

This passes your assertions.

Mark Byers
Well it did... until he changed his question!#%!!%!
Mark Byers
+1 because it *was* right at one point ;-)
joshua.ewer
+1 as well - thanks for the answer!
Jordan
+2  A: 

You just want to get the first level interfaces, right? You could mash up some LINQ and reflection; just exclude anything that the base type is implementing.

var fooType = typeof(Foo);

if(fooType.BaseType == null)
  return fooType.GetInterfaces().ToArray();

return fooType 
  .GetInterfaces()
  .Except(fooType.BaseType.GetInterfaces())
  .ToArray();
joshua.ewer
Hah. What Mark said. Though now that I see Andrew's answer, this isn't exactly what you're asking but it does pass your assertions.
joshua.ewer
This works for this case but can fail if the the class implements more than one interface because GetInterface does not return them in a consistent order.
Jamie Ide
@Jamie, that's true, and I'd say the question is poorly posed. He didn't generalize to the case where there are multiple interfaces implemented by the toplevel type.
Cheeso
@joshua.ewer I had to accept another answer. When I converted your code to a more general GetTopLevelInterfaces() method, like Cheeso did in his answer, your solution succeeded when I passed in typeof(Bar), but failed on typeof(Foo) (see Cheeso's answer).
Jordan
A: 

Note: Updated to also filter inherited interfaces.

You could exclude base interface members, like this:

public Type[] GetDeclaredInterfaces( Type type )
{
    if( type == typeof(object) )
        return new Type[ 0 ];    
    Type[] interfaces = type.GetInterfaces();
    Type[] baseInterfaces = interfaces.Where( i => i.BaseType != null && i.BaseType.IsInterface );
    Type[] declaredInterfaces = interfaces.Except( type.BaseType.GetInterfaces() );
    return declaredInterfaces.Except( baseInterfaces );
}
Morten Mertner
+2  A: 

Try this:

using System.Linq;    
public static class Extensions
{
    public static Type[] GetTopLevelInterfaces(this Type t)
    {
        Type[] allInterfaces = t.GetInterfaces();
        var selection = allInterfaces
            .Where(x => !allInterfaces.Any(y => y.GetInterfaces().Contains(x)))
            .Except(t.BaseType.GetInterfaces());
        return selection.ToArray();
    }
}

usage:

    private void Check(Type t, Type i)
    {
        var interfaces = t.GetTopLevelInterfaces();

        Debug.Assert(interfaces != null, "interfaces is null");
        Debug.Assert(interfaces.Length == 1, "length is not 1");
        Debug.Assert(interfaces[0] == i, "the expected interface was not found");

        System.Console.WriteLine("\n{0}", t.ToString());
        foreach (var intf in  interfaces)
            System.Console.WriteLine("  " + intf.ToString());

    }

    public void Run()
    {
        Check(typeof(Foo), typeof(IEnumerable<int>));
        Check(typeof(Bar), typeof(IDisposable));
    }

As noted elsewhere, this only works if the checked type explicitly implements a single interface. If you have more than one, then you need to change your Assert.

Cheeso