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

Shi*_*rin 18 .net c# oop inheritance

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

在此处输入图片说明

IList<T>

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
Run Code Online (Sandbox Code Playgroud)

ICollection<T>

public interface ICollection<T> : IEnumerable<T>, IEnumerable
Run Code Online (Sandbox Code Playgroud)

鉴于ICollection<T>已经包含IEnumerable<T>以及IEnumerable为什么IList<T>需要包含它们。不能简化如下吗?

public interface IList<T> : ICollection<T>
Run Code Online (Sandbox Code Playgroud)

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

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

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

Pan*_*vos 17

精简版

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

长版

why does IList need to inherit from both of them它没有。GitHub 中 .NET Old实际来源是:

public interface IList<T> : ICollection<T>
Run Code Online (Sandbox Code Playgroud)

.NET Core的源代码类似

public interface IList<T> : ICollection<T>
Run Code Online (Sandbox Code Playgroud)

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

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

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

Intellisense、参数信息、IDE 也显示所有已实现的接口,因为

编辑后

之所以会产生误解,是因为代码中继承的接口被编译器扩展了。这段代码:

interface IX{}
interface IY:IX{}

public class C :IY{
    public void M() {
    }
}
Run Code Online (Sandbox Code Playgroud)

在 Sharplab.io 中更改为

public class C : IY, IX
{
    public void M()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

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

.class public auto ansi beforefieldinit C
    extends [System.Private.CoreLib]System.Object
    implements IY,
               IX
{
Run Code Online (Sandbox Code Playgroud)

这表明从IX单独继承与从所有继承接口继承完全相同。

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

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

它在规范中

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

来自ECMA CIL 标准Interface Type Derivation部分 (1.8.9.11)

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

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


ang*_*son 13

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


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

  • ILSpy
  • 达斯姆

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

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

void Main()
{
}

public interface IA
{
}

public interface IB : IA
{
}

public class Test : IB
{
}
Run Code Online (Sandbox Code Playgroud)

然后要求 LINQPad 使用 ILSpy 反映代码,我们得到以下定义Test

public class Test: IB, IA
Run Code Online (Sandbox Code Playgroud)

显然,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
{
Run Code Online (Sandbox Code Playgroud)

基本上,这是编译器如何编译源代码的工件。我不知道足够的 IL 知道是否从中级语言重新组装界面,更不用说IA实际上会产生相同的输出,但我会将其作为练习。

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

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