【问题标题】:Why doesn't IList<T> only inherit from ICollection<T>?为什么 IList<T> 不只从 ICollection<T> 继承?
【发布时间】:2021-04-16 03:53:48
【问题描述】:

有趣的是,当我在 Visual Studio 中查看IList&lt;T&gt; 的定义时,它与 GitHub 上的源代码并不相同。

IList&lt;T&gt;

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

ICollection&lt;T&gt;

public interface ICollection<T> : IEnumerable<T>, IEnumerable

鉴于ICollection&lt;T&gt; 已经包含IEnumerable&lt;T&gt;IEnumerable 为什么IList&lt;T&gt; 需要包含它们。不能简化成下面这样吗?

public interface IList<T> : ICollection<T>

我试图理解这个长接口链背后的逻辑。

您可以查看下面的源代码以查看差异。

https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/collections/generic/ilist.cs#L37

【问题讨论】:

  • 我猜测一下,您已经使用 ILSpy 反编译了系统程序集,并看到 IList(或 &lt;T&gt;)列出了一些接口。这是 ILSpy 的神器。如果一个类实现了 IA 并且 IA 暗示了 IB,ILSpy 会将该类列为实现 IA 和 IB。参考源、github、文档,都只列出了ICollection。
  • “定义”您从哪里或如何获得此定义?` 我们有参考源、github 源(适用于 .NET 5)、ILSpy、dotPeek、文档。此信息有很多来源,请告诉我们您使用了哪个。让我说得更清楚一点:其中一些来源不同,而一些(ILSpy)甚至可能是错误的
  • @Alexander 这不是实际来源。那是元数据生成的代码。在 IL 中,所有接口都明确列出。请记住,接口是接口,就像墙上插座或音频插孔是接口一样。 3 针插座“继承”了 2 针插座接口。您仍然可以看到所有的孔和针脚。一个组合的麦克风+音频插孔“继承”了 2 个单声道和 1 个麦克风引脚。您仍然可以看到所有引脚,而不是“立体声”和“麦克风”引脚
  • Go to definition in Visual Studio 在后台使用ILSpy,ILSpy是错误的。它积极地将隐含的接口添加到类型中。您可以使用public interface IA { } public interface IB : IA { } public class Test : IB { } 对此进行测试,然后对其进行反编译。 Test 将被反编译显示为public class Test: IB, IA。这就是我们询问来源的原因。
  • ILSpy 不会返回源文件的准确副本。与原始源相比,有许多结构最终会成为不同的反编译源,这仅仅是因为编译器正在进行高级重写。因此,我不认为这是 ILSpy 的缺陷,它很可能完全按照设计工作。请注意,在我的 Test 示例中,typeof(Test).GetInterfaces() 返回 IAIBILDasm 也是如此

标签: c# .net oop inheritance


【解决方案1】:

短版

在 .NET 中,接口不形成层次结构树。当一个类型实现派生接口时,它实现了所有“父”接口。这是实际规范的一部分

加长版

why does IList need to inherit from both of them 没有。 actual source for .NET Old in GitHub 是:

public interface IList<T> : ICollection<T>

.NET Core is similar的来源

public interface IList<T> : ICollection<T>

这个问题没有解释多重继承的假设来自哪里。也许文档被误解了?

好的文档总是列出了一个类实现的所有接口。如果没有,程序员将不得不追踪多个链接以找出一个类做了什么、它实现了什么或专门的行为是什么。

事实上,2000 年左右的 COM 文档就是这样,将类和接口文档分开。那是在谷歌和在线文档之前,所以找出一个类做了什么真的很难。找出你需要实例化什么类来获得特定服务几乎是不可能的。

智能感知、参数信息、IDE 也会显示所有实现的接口,因为

编辑后

因此产生了误解,因为代码中的继承接口是由编译器扩展的。这段代码:

interface IX{}
interface IY:IX{}

public class C :IY{
    public void M() {
    }
}

changes into this in Sharplab.io

public class C : IY, IX
{
    public void M()
    {
    }
}

生成的 IL 显示相同的内容:

.class public auto ansi beforefieldinit C
    extends [System.Private.CoreLib]System.Object
    implements IY,
               IX
{

这说明单独从IX继承与从所有继承的接口继承完全一样。

.NET 中的接口实际上就是一个接口。就像墙上插座是一个接口,或者一个 4 针音频插孔是一个接口一样。 4 针音频插孔“继承”了 1 个立体声和 1 个麦克风连接。立体声连接“继承”了 2 个单声道连接。

虽然我们没有看到 2 个引脚组,但我们看到并使用了 2 个单声道和 1 个麦克风引脚。

在规范中

在 .NET 中,接口实际上是 API 规范,而不是实现。当一个类实现一个从其他接口派生的接口时,它实现了所有这些接口。接口不像类那样形成层次结构树。

来自the ECMA CIL standardInterface Type Derivation 部分 (1.8.9.11)

  • 对象类型形成一个单一的继承树;接口类型没有。
  • 对象类型继承指定如何继承实现; required 接口没有,因为接口没有定义实现。所需的接口指定了实现对象类型应支持的附加契约。

要突出最后一个区别,请考虑一个接口 IFoo,它只有一个方法。派生自它的接口 IBar 要求任何支持 IBar 的对象类型也支持 IFoo。它没有说明 IBar 本身将具有哪些方法。

【讨论】:

【解决方案2】:

TL;DR:编译器将编译类,就好像它专门将所有提到的接口以及所有隐含/继承的接口实现到程序集中一样。如果不实际下载并显示原始源代码,ILSpy、ILDasm 或“转到定义”就无法知道其中的区别。


由于您现在已经澄清您在 Visual Studio 中使用了“转到定义”,因此范围内有两个工具:

  • ILSpy
  • ILDasm

两者都采用不同的方法来显示已编译程序集的内容。我相信 ILSpy 在 Visual Studio 的幕后使用,但请继续阅读以了解为什么这实际上并不重要。

如果我们在LINQPad做一个简单的测试:

void Main()
{
}

public interface IA
{
}

public interface IB : IA
{
}

public class Test : IB
{
}

然后使用 ILSpy 让 LINQPad 反映代码,我们得到 Test 的这个定义:

public class Test: IB, IA

显然,ILSpy 显示 Test 实现了两者,而源代码刚刚通过 IB 获得了 IA

ILDasm 呢?我使用 Visual Studio 编写了一个 .NET 5 程序集,然后使用 ILDasm 对其进行反编译,代码与上面完全相同:

.class interface public abstract auto ansi ClassLibrary3.IA
{
} // end of class ClassLibrary3.IA

.class interface public abstract auto ansi ClassLibrary3.IB
       implements ClassLibrary3.IA
{
} // end of class ClassLibrary3.IB

.class public auto ansi beforefieldinit ClassLibrary3.Test
       extends [System.Runtime]System.Object
       implements ClassLibrary3.IB,
                  ClassLibrary3.IA
{

基本上,这是编译器如何编译源代码的工件。我不知道足够的 IL 知道是否从中间语言重新组装接口,不提IA 实际上会产生相同的输出,但我将把它留作练习。

我还查看了这些信息的各种来源:

  1. 参考源没有明确列出隐含的接口
  2. Github 源代码没有明确列出隐含接口
  3. IList 的文档没有,但for IList&lt;T&gt;
  4. ILSpy 反编译列出所有接口
  5. ILDasm 反编译列出所有接口(这应该是实际内容,所以我想说在编译的程序集级别无法区分差异)

【讨论】:

  • 参考来源是什么意思?可以举一些例子吗?
  • @Alexander Reference source.
  • @Alexander .NET Core 是开源的,因此源代码确实是构建 .NET Core 的实际源代码。对于 .NET Old(即框架),微软只发布了完成的源代码。它仍然是构建 .NET Old 的实际来源,但不接受 .NET Framework 团队之外的问题和拉取请求
  • 我知道,但是由于编译的程序集实际上并没有显示出差异,而且我们已经得到澄清,这只是关于 Visual Studio 中的转到定义,我认为这只是额外信息
猜你喜欢
  • 2023-03-29
  • 1970-01-01
  • 2020-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多