Подтвердить что ты не робот

Как работает IEnumerable <T>.ToArray()?

Является ли это двухпроходным алгоритмом? то есть он повторяет перечислимый один раз, чтобы подсчитать количество элементов, чтобы он мог выделить массив, а затем снова передать их, чтобы вставить их?

Выполняет ли цикл один раз и сохраняет размер массива?

Или использует промежуточную структуру, такую ​​как List (который, вероятно, внутренне изменяет размер массива)?

4b9b3361

Ответ 1

Он использует промежуточную структуру. Фактический тип - буфер, который является внутренней структурой в рамках. На практике у этого типа есть массив, который копируется каждый раз, когда он заполнен, чтобы выделить больше места. Этот массив начинается с длины 4 (в .NET 4, это детали реализации, которые могут измениться), поэтому вы можете в конечном итоге распределять и копировать много, когда делаете ToArray.

Однако есть оптимизация. Если источник реализует ICollection<T>, он использует Count для этого, чтобы выделить правильный размер массива с самого начала.

Ответ 2

Сначала он проверяет, является ли источник 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;
}

Ответ 3

Подобно этому (через .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;
    }
}

Ответ 4

Во-первых, элементы загружаются во внутренний класс Buffer<T>, который позволяет генерировать счетчик

Далее вызывается Buffer<T>.ToArray, который делает и Array.Copy массива Buffer<T> в возвращаемом массиве.

.NET Reflector показывает этот код, если вы хотите сами убедиться.

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

Ответ 5

В общем, попытка повторить перечислимое дважды может привести к катастрофе, поскольку нет гарантии, что перечислимый может быть повторен во второй раз. Поэтому, выполняя a Count, а затем выделите, тогда копия не будет.

В Reflector он показывает, что он использует тип под названием Buffer, который эффективно передает последовательность в изменение размера массива (удваивание при каждом перераспределении, чтобы количество перераспределений было O(log n)) по мере необходимости, а затем возвращало подходящий размер массив, когда он достигает конца