为什么 IList<T> 不只从 ICollection<T> 继承?

时间:2021-01-11 07:35:31

标签: c# .net oop inheritance

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

enter image description here

IList<T>

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

ICollection<T>

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

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

public interface IList<T> : ICollection<T>

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

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

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

2 个答案:

答案 0 :(得分:17)

短版

在 .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 文档就是这样,将类和接口文档分开。那是在 Google 和在线文档出现之前,所以找出一个班级做了什么真的很难。找出需要实例化以获得特定服务的类几乎是不可能的。

Intellisense、参数信息、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 本身将具有哪些方法。

答案 1 :(得分:13)

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
{
}

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

public class Test: IB, IA

显然 ILSpy 显示 Test 实现了两者,而源只是通过 IA 获得 IB

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<T>
  4. ILSpy 反编译列出所有接口
  5. ILDasm 反编译列出所有接口(这应该是实际 的内容,所以我想说在编译的程序集级别无法区分差异)