Updated question given Andrew Hare's correct answer:
Given the following C# classes:
public class Bar : Foo, IDisposable
{
// implemen开发者_运维技巧tation 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.
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.
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.
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();
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.
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 );
}
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>));
精彩评论