IEnumerable<> 与 List<> 作为参数

IEnumerable<> vs List<> as a parameter

一般我在传参时倾向于使用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)
    {
    }
}
Method Job Runtime Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
EnumeratingCollectionsBad .NET Core 3.1 .NET Core 3.1 17.802 us 0.3670 us 1.0764 us 17.338 us 6.3782 - - 40032 B
EnumeratingCollectionsFixed .NET Core 3.1 .NET Core 3.1 5.015 us 0.1003 us 0.2535 us 4.860 us - - - 32 B

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

Why would the interface version be so much slower (and memory intensive) than the concrete version?

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

因此,即使两种方法都对同一类型的对象 (List<T>) 进行操作,但其中一种方法调用此方法:

IEnumerator<T> GetEnumerator()

...有人称之为:

List<T>.Enumerator GetEnumerator()

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

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

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