System.Linq.Enumerable.Reverse是否将内部的所有元素复制到数组中?

Die*_*ego 12 .net linq .net-4.0

几年前,有人抱怨实施,Linq.Reverse()微软承诺解决这个问题.这是在2008年,所以问题是,框架4是否有一个优化的实现,Linq.Reverse()当集合类型允许时(例如IList<T>),没有实现集合(即将所有元素复制到内部数组)?

svi*_*ick 13

显然,不可能优化所有案例.如果某个对象仅实现IEnumerable<T>而不实现IList<T>,则必须迭代它直到结束才能找到最后一个元素.因此优化仅适用于实现的类型IList<T>(如T[]List<T>).

现在,它实际上在.NET 4.5 DP优化?让我们启动Reflector ILSpy:

public static IEnumerable<TSource> Reverse<TSource>(
    this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return ReverseIterator<TSource>(source);
}
Run Code Online (Sandbox Code Playgroud)

好的,ReverseIterator<TSource>()看起来怎么样?

private static IEnumerable<TSource> ReverseIterator<TSource>(
    IEnumerable<TSource> source)
{
    Buffer<TSource> buffer = new Buffer<TSource>(source);
    for (int i = buffer.count - 1; i >= 0; i--)
    {
        yield return buffer.items[i];
    }
    yield break;
}
Run Code Online (Sandbox Code Playgroud)

迭代器块的作用是Buffer<T>为集合创建一个并向后迭代.我们快到了,有什么Buffer<T>

[StructLayout(LayoutKind.Sequential)]
internal struct Buffer<TElement>
{
    internal TElement[] items;
    internal int count;
    internal Buffer(IEnumerable<TElement> source)
    {
        TElement[] array = null;
        int length = 0;
        ICollection<TElement> is2 = source as ICollection<TElement>;
        if (is2 != null)
        {
            length = is2.Count;
            if (length > 0)
            {
                array = new TElement[length];
                is2.CopyTo(array, 0);
            }
        }
        else
        {
            foreach (TElement local in source)
            {
                if (array == null)
                {
                    array = new TElement[4];
                }
                else if (array.Length == length)
                {
                    TElement[] destinationArray = new TElement[length * 2];
                    Array.Copy(array, 0, destinationArray, 0, length);
                    array = destinationArray;
                }
                array[length] = local;
                length++;
            }
        }
        this.items = array;
        this.count = length;
    }

    // one more member omitted
}
Run Code Online (Sandbox Code Playgroud)

我们在这里有什么?我们将内容复制到数组中.在每种情况下.唯一的优化是,如果我们知道Count(即集合实现ICollection<T>),我们就不必重新分配数组.

因此,对于优化IList<T>不是在.NET 4.5 DP.它会在每种情况下创建整个集合的副本.

如果我猜测为什么它没有被优化,在阅读Jon Skeet关于这个问题的文章之后,我认为这是因为优化是可观察的.如果在迭代时改变集合,您将看到更改后的数据与优化,但没有它的旧数据.实际上以微妙的方式改变某些行为的优化是一件坏事,因为它具有向后兼容性.

  • 突破性改变?在我看来,这仅适用于那些做出假设 * 不受文档支持 * 的人,这些假设在 * 任何 * 情况下都会咬你。比如说,如果`Dictionary` 停止维护秩序(只要你不删除任何东西,它目前就会停止),你首先就不应该依赖这种无证行为。在我看来,可以轻松应用“Enumerable.Reverse()”优化。 (2认同)