You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LINQ中Enumerable.Repeat与Last导致副作用执行次数不符预期的原因

LINQ中Enumerable.Repeat与Last方法的异常行为解析

问题重现

测试代码

[Test]
public void Test()
{
    var i = 0;
    var v = Enumerable.Repeat(0, 10).Select(x =>
    {
        i++;
        return x;
    }).Last();

    Assert.That(v, Is.EqualTo(0));
    Assert.That(i, Is.EqualTo(10));
}

测试失败结果

Message:
Expected: 10
But was:  1

对比实现

替换为自定义的MyRepeat或MyLast后测试均可通过:

private T MyLast<T>(IEnumerable<T> values)
{
    var enumerator = values.GetEnumerator();
    while (enumerator.MoveNext())
    {
    }
    return enumerator.Current;
}

private IEnumerable<T> MyRepeat<T>(T value, int count)
{
    for(var i = 0; i<count; ++i)
    {
        yield return value;
    }
}

(测试基于.NET 6,最初发现于.NET Core 3.1环境)

原因解析

1. Enumerable.Repeat的特殊实现

.NET中的Enumerable.Repeat并非像自定义的MyRepeat那样通过yield逐个生成元素,而是返回一个实现了IList<T>接口的自定义集合类型(如RepeatIterator<T>)。这个集合支持直接通过索引访问元素,无需遍历整个序列。

2. Enumerable.Last的智能优化

针对Select后调用Last的场景,.NET LINQ会进行针对性优化:当Select的源序列实现了IList<T>时,Last方法不会遍历整个Select序列,而是直接获取源序列的最后一个元素,仅对该元素执行一次Select的转换委托。

具体执行流程

当执行Enumerable.Repeat(0,10).Select(x => {i++; return x;}).Last()时:

  • Enumerable.Repeat返回一个包含10个0的IList<T>集合
  • Last方法检测到Select的源是可索引的IList<T>,直接通过索引[9]获取源的最后一个元素
  • 仅对该元素执行一次Select的委托逻辑,i仅递增1次
  • 最终返回转换后的元素,导致测试中i的值为1而非预期的10

而自定义的MyRepeat是普通的yield迭代器(未实现IList<T>),MyLast也没有这种优化逻辑,因此会遍历整个序列,i递增10次,测试通过。

内容的提问来源于stack exchange,提问作者Earth Engine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:14:56