Как работает IEnumerable <T>.ToArray()?
Является ли это двухпроходным алгоритмом? то есть он повторяет перечислимый один раз, чтобы подсчитать количество элементов, чтобы он мог выделить массив, а затем снова передать их, чтобы вставить их?
Выполняет ли цикл один раз и сохраняет размер массива?
Или использует промежуточную структуру, такую как List (который, вероятно, внутренне изменяет размер массива)?
Ответы
Ответ 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)
) по мере необходимости, а затем возвращало подходящий размер массив, когда он достигает конца