繁体   English   中英

IEnumerable 是怎么做的<T> .ToArray() 工作吗?

[英]How does IEnumerable<T>.ToArray() work?

它是两遍算法吗? 即,它迭代 enumerable 一次以计算元素的数量,以便它可以分配数组,然后再次传递以插入它们?

它是否循环一次,并不断调整数组大小?

或者它是否使用像 List 这样的中间结构(它可能在内部调整数组的大小)?

它使用中间结构。 实际涉及的类型是 Buffer,它是框架中的内部结构。 实际上,这种类型有一个数组,每次它满时都会复制它以分配更多空间。 此数组的长度为 4(在 .NET 4 中,这是一个可能会更改的实现细节),因此在执行 ToArray 时您可能最终会分配和复制很多内容。

不过,有一个优化。 如果源实现ICollection<T> ,它会使用 Count 从一开始就分配正确的数组大小。

首先它检查源是否是ICollection<T> ,在这种情况下它可以调用源的ToArray()方法。

否则,它只枚举一次源。 当它枚举时,它将项目存储到缓冲区数组中。 每当它到达缓冲区数组的末尾时,它就会创建一个两倍大小的新缓冲区并复制旧元素。 枚举完成后,它返回缓冲区(如果它的大小正好合适)或将项目从缓冲区复制到一个大小合适的数组中。

这是该操作的伪源代码:

public static T[] ToArray<T>(this IEnumerable<T> source)
{
    T[] items = null;
    int count = 0;

    foreach (T item in source)
    {
        if (items == null)
        {
            items = new T[4];
        }
        else if (items.Length == count)
        {
            T[] destinationArray = new T[count * 2];
            Array.Copy(items, 0, destinationArray, 0, count);
            items = destinationArray;
        }
        items[count] = item;
        count++;
    }

    if (items.Length == count)
    {
        return items;
    }
    T[] destinationArray = new TElement[count];
    Array.Copy(items, 0, destinationArray, 0, count);
    return destinationArray;
}

像这样(通过 .NET Reflector):

public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    Buffer<TSource> buffer = new Buffer<TSource>(source);
    return buffer.ToArray();
}

[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;
    }

    internal TElement[] ToArray()
    {
        if (this.count == 0)
        {
            return new TElement[0];
        }
        if (this.items.Length == this.count)
        {
            return this.items;
        }
        TElement[] destinationArray = new TElement[this.count];
        Array.Copy(this.items, 0, destinationArray, 0, this.count);
        return destinationArray;
    }
}

首先,将项目加载到允许生成计数的内部类Buffer<T>

接下来,调用Buffer<T>.ToArray ,它将Buffer<T>的数组的Array.Copy到返回的数组中。

如果您想亲自查看,.NET Reflector 会显示此代码。

http://www.red-gate.com/products/reflector/

通常,尝试对可枚举项进行两次迭代可能会导致灾难,因为无法保证可以对可枚举项进行第二次迭代。 因此,执行Count然后分配然后复制就行了。

在 Reflector 中,它表明它使用一种称为Buffer的类型,该类型根据需要有效地将序列流式传输到调整大小的数组中(每次重新分配时加倍,以便重新分配的次数为O(log n) ),然后在需要时返回适当大小的数组到达终点

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM