在Batch方法中使用Select(x => x)的目的是什么?

时间:2014-12-11 13:38:08

标签: c# linq

我正在查看Batch方法的源代码,我已经看到了这个:

// Select is necessary so bucket contents are streamed too
yield return resultSelector(bucket.Select(x => x));

有一条我不太了解的评论。我在不使用Select的情况下测试了这种方法,效果很好。但似乎有些东西我不知道。我无法想到任何必要的例子,那么在这里使用Select(x => x)的实际目的是什么?

以下是完整的参考源代码:

private static IEnumerable<TResult> BatchImpl<TSource, TResult>(
        this IEnumerable<TSource> source,
        int size,
        Func<IEnumerable<TSource>, TResult> resultSelector)
    {
        TSource[] bucket = null;
        var count = 0;

        foreach (var item in source)
        {
            if (bucket == null)
                bucket = new TSource[size];

            bucket[count++] = item;

            // The bucket is fully buffered before it's yielded
            if (count != size)
                continue;

            // Select is necessary so bucket contents are streamed too
            yield return resultSelector(bucket.Select(x => x));

            bucket = null;
            count = 0;
        }

        // Return the last bucket with all remaining elements
        if (bucket != null && count > 0)
            yield return resultSelector(bucket.Take(count));
    }

1 个答案:

答案 0 :(得分:5)

总结评论中的内容,理论上这是多余的。在这种情况下,延迟执行无关紧要。在yield已完成执行时,已经计算出bucket的内容,并且没有任何内容可以推迟。

迭代器块行为也没有问题 - 每次我们回到这个实现时,bucket都会被重置并重新创建(bucket = null之后立即yield)。即使有人将结果转换为数组类型并对其进行修改,我们也不在乎。

这种方法的优点似乎只是优雅:在resultSelector的所有调用之间存在类型一致性。如果没有“冗余”Select,实际类型大部分时间都为TSource[],而IEnumerable<TSource>表示没有填充整个bucket的尾随元素。< / p>

但是,可以想象以下情况:

  1. 使用此功能的人注意到实际类型是数组
  2. 由于需要提高性能,他们会将收到的批次转换为TSource[](例如,他们现在可以更有效地跳过元素,as Skip is not optimized for arrays
  3. 他们使用该方法没有任何问题,因为在他们的情况下发生了Count() % size == 0
  4. 直到稍后,才会弹出一个额外的元素,导致最后一个yield被执行。现在,TSource[]的演员阵容将失败。

    因此,根据元素的数量和size,该方法在结果类型方面的行为不一致(传递给给定的回调)。可以想象其他复杂的情况,这种不一致可能会导致麻烦,例如某些ORM,根据实际类型,将对象序列化到不同的表中。在这种情况下,数据将最终出现在不同的表格中。

    这些场景当然都是基于其他一些错误,并没有证明没有Select实现是错误的。然而,在某种意义上,它与Select更加友好,它将此类不幸情景的数量减少到最低限度。