(семь лет спустя ...)
Несколько других (хороших) ответов были сконцентрированы на разнице в микроскопических характеристиках.
Этот пост является просто дополнением к упоминанию семантической разницы , которая существует между IEnumerator<T>
, созданным массивом (T[]
), по сравнению с возвращаемым List<T>
.
Лучше всего иллюстрируется на примере:
IList<int> source = Enumerable.Range(1, 10).ToArray(); // try changing to .ToList()
foreach (var x in source)
{
if (x == 5)
source[8] *= 100;
Console.WriteLine(x);
}
Приведенный выше код будет работать без исключения и выдает результат:
1
2
3
4
5
6
7
8
900
10
Это показывает, что IEnumarator<int>
, возвращаемый int[]
, не отслеживает, был ли массив изменен с момента создания перечислителя.
Обратите внимание, что я объявил локальную переменную source
как IList<int>
. Таким образом, я убедился, что компилятор C # не оптимизирует оператор foreach
в нечто, эквивалентное циклу for (var idx = 0; idx < source.Length; idx++) { /* ... */ }
. Это то, что может сделать компилятор C #, если я использую var source = ...;
. В моей текущей версии .NET Framework фактический перечислитель, используемый здесь, является закрытым ссылочным типом System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
, но, конечно, это деталь реализации.
Теперь, если я поменяю .ToArray()
на .ToList()
, я получу только:
1
2
3
4
5
, за которым следует System.InvalidOperationException
поговорка:
Коллекция была изменена; операция перечисления может не выполняться.
Базовым перечислителем в этом случае является общедоступный изменяемый тип значения System.Collections.Generic.List`1+Enumerator[System.Int32]
(в данном случае заключенный в рамку IEnumerator<int>
, потому что я использую IList<int>
).
В заключение, перечислитель, созданный List<T>
, отслеживает, изменяется ли список во время перечисления, в то время как перечислитель, созданный T[]
, этого не делает. Поэтому учитывайте эту разницу при выборе между .ToList()
и .ToArray()
.
Люди часто добавляют один дополнительный .ToArray()
или .ToList()
, чтобы обойти коллекцию, которая отслеживает, была ли она изменена в течение срока службы счетчика.
(Если кто-то хочет знать , как List<>
отслеживает, была ли изменена коллекция, в этом классе есть личное поле _version
, которое изменяется каждый раз, когда обновляется List<>
. )