【问题标题】:Type.GetInterfaces() for declared interfaces onlyType.GetInterfaces() 仅用于声明的接口
【发布时间】:2012-04-28 05:25:27
【问题描述】:

首先,有很多这样的问题,也许有些 OP 甚至问过同样的问题。问题是这些问题的答案(接受与否)实际上没有回答这个问题,至少我找不到。

如何确定一个类直接声明的接口,而不是由父类或声明的接口继承的接口?

例如

interface I {}
interface W : I {}
class C : W {}
class D : C, I {}
class E : D {}

结果:

  1. C 声明 W
  2. D 声明 I
  3. E 没有声明

可接受的解决方案可能要求接口至少具有一种方法。

如果您认为这确实是不可能的,请注意不要犯this 错误,实际上can 会这样做。

InterfaceMap 处理很多情况,但不是全部(我在下面给出了一个 InterfaceMap 无法解决的示例)。我有一个想法,但不知道如何实现它,是反编译类的字节码并查看声明的内容,因为 ILSpy 等工具可以正确识别每种情况!如果您喜欢这个想法,请给我一个链接,以获取该领域的更多信息。

我希望你们中的一些人会建议我清理我的设计。如果这不是您的论点,那么帖子的其余部分与您无关。

我项目的部分目的是跟踪给定类型的潜在代码路径(在运行时)。为了以编程方式确定将在目标类型上调用哪个方法,而不实际调用该方法或创建目标类型的实例,了解目标类型的声明接口对于确定性地解决此问题是必要的。 “不”你说?考虑:

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

正确的答案是2,但是如果你将E的声明改为不直接包含I,答案是1。现在确定这是边缘情况,但它也是正确的答案。因此,我的引擎与潜在的用户代码不完全兼容。告诉用户清理他们的代码以使用我的工具是不可接受的。 (请注意,还有许多有趣的关于强制转换为接口的规则,但我不会在这里讨论它们。

【问题讨论】:

  • 问题底部的代码示例毫无意义。当您写new E() 时,您期望会发生什么?
  • 谢谢,已修复,E应该继承D
  • 如果它真的是多余的,它不会改变测试的结果。
  • 还有一个问题,当您说Unfortunately (part of) my project's purpose is in tracing code paths without having to run the code 时,这是否意味着您没有创建这些对象,而您的项目正在读取原始文本 .cs 文件?
  • 您想知道一个类重新实现了哪些接口?见blogs.msdn.com/b/ericlippert/archive/2011/12/08/…

标签: c# reflection


【解决方案1】:

获取给定类型的声明接口,您可以在给定类型上使用GetInterfaces,然后如果它有BaseType,您可以使用Except 枚举器来排除基类型的接口...

它未经测试,但可能类似于这种扩展方法......

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

【讨论】:

  • 不幸的是,这不起作用。请注意,派生类型可以选择再次声明接口,这会影响调用哪些方法(参见结尾示例)。
【解决方案2】:

根据来自 cmets 的有用信息,我能够明确地证明这不能通过 msft 反射完成(尽管可以使用 mono.cecil)。原因是GetInterfaces 调用进行了本机调用,该调用返回为目标类型预先展平的接口。

【讨论】:

  • 您能否指出通过 mono.cecil 解决此问题的解决方案或文档?
【解决方案3】:

这个怎么样?

Type type = typeof(E);
var interfaces = type.GetInterfaces()
    .Where(i => type.GetInterfaceMap(i).TargetMethods.Any(m => m.DeclaringType == type))
    .ToList();

【讨论】:

  • 我也试过这个,所以我喜欢你的尝试。除非派生类型实际上从接口声明了方法,否则这不起作用。在最后一个示例中(使用EI),E 不映射到接口中的任何方法。还有其他情况 InterfaceMap 是不够的。我创建了一个测试用例来帮助证明我的尝试。看到这个pastebin:pastebin.com/fuhzKRMw
  • @payo 我已经在您的示例中尝试过,效果很好。但我现在要在你的其他测试用例上测试它。
  • 你是对的,这确实适用于给定的示例,但并非适用于所有情况。我正在审查为什么这对示例有效,这很有趣。
  • 似乎当有一个未映射的父级时,子级(也未映射)失去准确性。我在想一些未映射的基数的递归可能足以回答这个问题,但是一个非常相似的变化。我将对此进行更彻底的测试。令我感兴趣的是,我查看了 DeclaringType 并发现它是不够的。然而,看到它在这种情况下工作让我重新审视失败的案例。
  • 这还不够成功。问题退化为歧义,递归不是解决方案。我感到害怕失败。我调查了 ILSpy,他们只是使用单声道作为单声道作为更具描述性的反射元数据。我也会考虑使用单声道运行时。
【解决方案4】:

为了以编程方式确定将在目标类型上调用哪个方法,而不实际调用该方法或创建目标类型的实例,了解目标类型的声明接口对于确定性地解决此问题是必要的。

Reflection API 通过Type.GetInterfaceMap(Type) 提供了这个功能。例如,在您的情况下,typeof(E).GetInterfaceMap(typeof(I)) 为您提供以下信息,可让您直接确定映射:

InterfaceMethods            TargetMethods
[0]: I.Foo()                [0]: E.I.Foo()

很明显,TargetMethods 会准确告诉您如果实例在运行时的类型为 E,则调用哪个方法。

您会注意到接口方法I.Foo() 实际上映射到类型E 上显式实现I.Foo() 的方法。 This method was inserted by the compiler and it just calls D.Foo().

(更新:糟糕,正如您在屏幕截图中看到的那样,我将第二个 Foo() 设为虚拟。但我得到的结果与非虚拟完全相同。)

【讨论】:

  • 嗯,包括 .net 符号我在堆栈中看到 E。现在对此进行更多研究。
  • 赞成澄清,但不是原始问题的答案。
  • 没关系,在某些情况下它仍然模棱两可。太糟糕了,我希望您对 InterfaceMap 的补充见解就足够了。感谢您的帮助。
  • 我想指出这不是问题的答案。尽管您已经表明在所有情况下都可以预先确定路径,但问题是如何列出已声明的接口。寻路只是实现这一目的的一个原因。令我惊讶的是 Mono.Cecil 公开了这些信息。
【解决方案5】:

我认为问题不在于GetInterfaces 给了你错误的信息。问题是你看错了类型。

当你有(p as I).Foo()这样的代码时,你应该看看typeof(I)的方法:不是p.GetType(),也不是typeof(C),也不是typeof(E)

反思I 将告诉您正确解决呼叫所需的一切,尽管这绝不是微不足道的。

【讨论】:

  • GetInterfaces() 为另一个用例提供了正确的信息,是的。不幸的是, typeof(I) 没有告诉我 E 是否直接声明了 I。测试代码有和没有 E 显式声明它继承 I。
猜你喜欢
  • 1970-01-01
  • 2013-08-08
  • 2022-12-09
  • 2011-09-15
  • 2019-07-16
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-28
相关资源
最近更新 更多