【问题标题】:How do I enumerate a list of interfaces that are directly defined on an inheriting class?如何枚举直接在继承类上定义的接口列表?
【发布时间】:2011-01-31 21:41:34
【问题描述】:

根据 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&lt;int&gt;IEnumerableIDisposable),而不仅仅是顶层。

【问题讨论】:

  • 你为什么要这样做?断言 Bar 实现 IDisposable 不是更好更简洁吗?
  • @Svish - 这是一个人为的例子。我正在开发一个自定义 IoC 自动绑定工具,该工具需要寻找最顶级的接口。我想我会简化这个场景来回答我所追求的根本问题,而不是用 IoC 噪音把它弄得一团糟。另外,现在当其他人需要在不同的上下文中回答这个问题时,IoC 噪音不会妨碍他们。

标签: c# reflection class interface


【解决方案1】:

试试这个:

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

如其他地方所述,这仅在检查类型显式实现单个接口时才有效。如果您有多个,那么您需要更改您的 Assert。

【讨论】:

    【解决方案2】:

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

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

    这会通过你的断言。

    【讨论】:

    • 好吧……直到他改变了他的问题!#%!!%!
    【解决方案3】:

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

    var fooType = typeof(Foo);
    
    if(fooType.BaseType == null)
      return fooType.GetInterfaces().ToArray();
    
    return fooType 
      .GetInterfaces()
      .Except(fooType.BaseType.GetInterfaces())
      .ToArray();
    

    【讨论】:

    • 哈。马克说的。虽然现在我看到了 Andrew 的回答,但这并不是你要问的,但它确实通过了你的断言。
    • 这适用于这种情况,但如果该类实现了多个接口,则可能会失败,因为 GetInterface 不会以一致的顺序返回它们。
    • @Jamie,这是真的,我想说这个问题提出的不好。他没有概括到顶层类型实现了多个接口的情况。
    • 我不得不接受另一个答案。当我将您的代码转换为更通用的 GetTopLevelInterfaces() 方法时,就像 Cheeso 在他的回答中所做的那样,当我传入 typeof(Bar) 时,您的解决方案成功,但在 typeof(Foo) 上失败(请参阅 Cheeso 的回答)。
    【解决方案4】:

    实际上没有任何方法可以做到这一点,因为您要从接口层次结构中检索所有接口。这意味着当您实现IEnumerable&lt;T&gt; 时,您也隐式地实现了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&lt;T&gt;,编译器也发出了指示您的类型实现IEnumerable&lt;T&gt;IEnumerable 的IL。

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

    【讨论】:

    • 安德鲁 - 你的答案是正确的;我的问题并不像它需要的那样完整。谢谢!
    • 其实可以通过查看接口类型的BaseType来过滤这些。在别处查看我的答案。
    【解决方案5】:

    注意:更新后还可以过滤继承的接口。

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

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

    【讨论】:

      【解决方案6】:

      我会这样写:

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

      【讨论】:

      • 如何在不更改断言的情况下编写它?
      • 我不会。第一个断言(空检查)将始终通过(尽管它没有害处),第二个断言只有在您仅在您的控制下实现接口时才有意义,第三个断言(通过索引访问)在文档中被特别警告。
      • true,但他仅将这些断言专门用于他的原始 Foo 类型,该类型仅显式实现了一个接口。您的“安全代码”是重言式。它有点回避整个问题。
      猜你喜欢
      • 1970-01-01
      • 2019-10-15
      • 2016-11-22
      • 1970-01-01
      • 1970-01-01
      • 2019-11-16
      • 2020-02-23
      • 1970-01-01
      • 2016-09-12
      相关资源
      最近更新 更多