ELI5:将 IEnumerable 转换为列表或数组如何避免 IEnumerable 的多次枚举的性能损失?

ELI5: How does converting an IEnumerable to a list or array avoid the performance penalty of multiple enumeration of IEnumerable?

Resharper 不断抱怨:可能对 IEnumerable 进行多重枚举。例如:

    private int ParseLoanNumber(IEnumerable<string> lines)
    {
        var loanNumber = 0;

        var item = lines.FirstOrDefault(l => l.StartsWith(" LN#    00"));

        if (item != null)
        {
            loanNumber = item.ParseInt(8, 10).GetValueOrDefault();
        }
        else
        {
            item = lines.FirstOrDefault(l => l.StartsWith(" LOAN-NO (CONT'D)  00"));
            if (item != null)
            {
                loanNumber = item.ParseInt(19, 10).GetValueOrDefault();
            }
        }
        // Yada yada...
    }

推荐的解决方案是将 enumerable 转换为 listarray,然后对其进行迭代。

这让我很困惑。您仍然会枚举一些东西,并且两种类型(数组和列表)都实现 IEnumerable。那么这如何解决任何问题或以任何方式提高性能?

因为你可以这样写:

public IEnumerable<int> GetNumbersSlowly()
{
    for (var i = 0; i < 100; i++)
    {
        Thread.Sleep(10000); //Or retrieve from a website, etc
        yield return i;
    }
}

如果你这样使用它:

var numbers = GetNumbersSlowly();
foreach(var number in numbers) { 
    //Do something 
}
foreach(var number in numbers) { 
    //Do something 
}

这意味着每个数字完成的工作(睡眠)两次。计算可枚举一次并将其存储在数组或列表中意味着您确定没有对 return 项目进行额外处理。

由于您正在接受IEnumerable<string>,您确实不知道来电者没有进行上述操作。

如果您认为我的示例可能很少见或属于边缘情况,它也适用于以下情况:

var someSource = new List<int> { 1, 2, 3, 4, 5 };
var numbers = someSource.Select(s => s * 100000);

现在每次迭代 numbers 时,您也在重新进行计算。在这种情况下,它的工作量并不大,为什么要做的比你需要的多(而且它是非常重要的工作并不少见)。