IEnumerable<> vs List<> 作为参数

问题描述

一般来说,当我传入参数时,我倾向于使用 IEnumerable<> 作为类型。但是根据 BenchmarkDotNet:

[Benchmark]
public void EnumeratingCollectionsBad()
{
    var list = new List<string>();
    for (int i = 0; i < 1000; i++)
    {
        Bad(list);
    }
}

[Benchmark]
public void EnumeratingCollectionsFixed()
{
    var list = new List<string>();
    for (int i = 0; i < 1000; i++)
    {
        Fixed(list);
    }
}

private static void Bad(IEnumerable<string> list)
{
    foreach (var item in list)
    {
    }
}

private static void Fixed(List<string> list)
{
    foreach (var item in list)
    {
    }
}
方法 工作 运行时 平均 错误 StdDev 中位数 Gen 0 Gen 1 第 2 代 已分配
EnumeringCollectionsBad .NET Core 3.1 .NET Core 3.1 17.802 us 0.3670 us 1.0764 us 17.338 us 6.3782 - - 40032 B
EnumeringCollectionsFixed .NET Core 3.1 .NET Core 3.1 5.015 us 0.1003 us 0.2535 us 4.860 us - - - 32 B

为什么界面版本会比具体版本慢很多(并且占用大量内存)?

解决方法

为什么界面版本会比具体版本慢很多(并且占用大量内存)?

当它使用接口时,迭代必须在堆上分配一个对象......而List<T>.GetEnumerator()返回一个List<T>.Enumerator,它是一个结构,不需要任何额外的分配。 List<T>.Enumerator 实现了 IEnumerator<T>,但是因为编译器直接知道具体类型,所以不需要装箱。

因此,即使两种方法都在同一类型的对象(List<T>)上操作,但仍会调用此方法:

IEnumerator<T> GetEnumerator()

...有人称之为:

List<T>.Enumerator GetEnumerator()

第一个几乎可以肯定只是委托给第二个,但必须将结果装箱,因为 IEnumerator<T> 是引用类型。

事实上,List<T>.GetEnumerator() 返回一个可变结构 can have some surprising consequences,但它的设计正是为了获得您在此处看到的性能优势。

使用接口与具体类型本身可能会产生一些非常小的性能损失,但这里的主要原因是分配的差异。