如何枚举在继承类上直接定义的接口列表?

时间:2010-03-25 19:35:19

标签: c# reflection class interface

根据Andrew Hare的正确答案更新了问题:

给出以下C#类:

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

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

我想要一个类似下面的方法,它不会在断言上失败(注意:你不能改变断言):

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));
}

有人可以通过修复此方法来帮助断言不会失败吗?

调用typeof(Bar).GetInterfaces()不起作用,因为它返回整个界面层次结构(即interfaces变量包含IEnumerable<int>IEnumerableIDisposable),而不是只是顶级。

6 个答案:

答案 0 :(得分:8)

试试这个:

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();
    }
}

用法:

    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));
    }

如其他地方所述,这仅在checked类型显式实现单个接口时才有效。如果您有多个,则需要更改断言。

答案 1 :(得分:2)

由于您从接口层次结构中检索所有接口,因此实际上没有任何方法可以执行此操作。这意味着当您实施IEnumerable<T>时,您也隐式实施IEnumerable

换句话说,如果你看一下你创建的类的IL,你会看到:

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

即使您只表明您的类型实现了IEnumerable<T>,编译器也会发出IL,指示您的类型实现IEnumerable<T>IEnumerable

反射API很高兴地返回你在类型上实际定义的内容(这是你的类型实现两个接口 - 它实际上是这样做的)。 C#编译器允许您仅引用接口层次结构中最底层的类型,因为它将填充您的类型也实现的其他接口。这是接口继承与类型继承的不同方式之一。

答案 2 :(得分:2)

Andrew Hare是正确的,您无法使用反射检索指定的接口列表。但是,您可以通过排除其他人隐含的任何接口来找到“顶级”接口。你可以像这样实现它:

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

这会传递你的断言。

答案 3 :(得分:2)

你只想获得第一级接口,对吧?你可以混搭一些LINQ和反射;只是排除基类型正在实现的任何内容。

var fooType = typeof(Foo);

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

return fooType 
  .GetInterfaces()
  .Except(fooType.BaseType.GetInterfaces())
  .ToArray();

答案 4 :(得分:0)

我会把它写成:

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

但是如果不知道你想要测试什么,就很难回答。无论如何,在使用GetInterfaces时你应该not rely on the order,如果类型没有实现任何类型,那么该方法将返回一个空数组,因此不需要进行空检查。

编辑:如果你真的无法改变断言,那么安全的做法是:

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

答案 5 :(得分:0)

注意:已更新以过滤继承的接口。

您可以排除基本接口成员,如下所示:

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 );
}