Я читал этот комментарий Касабланки в https://softwareengineering.stackexchange.com/a/411324/109967:
Здесь есть еще одно предостережение: типы значений упаковываются в кучу, если доступ к ним осуществляется через интерфейс, поэтому при перечислении через IList или IEnumerable вам все равно придется выделять кучу. Вам нужно будет удерживать конкретный экземпляр List, чтобы избежать выделения.
Я хотел проверить эту теорию с помощью этого консольного приложения .NET 5 (я пробовал переключаться между List<T>
и IList<T>
):
using System;
using System.Collections.Generic;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
IList<int> numbers = new List<int> { 0, 1, 2, 3, 4, 5 };
//List<int> numbers = new List<int> { 0, 1, 2, 3, 4, 5 };
for (int i = 0; i < 2000; i++)
{
foreach (var number in numbers)
{
}
}
GC.Collect();
Console.WriteLine("GC gen 0 collection count = " + GC.CollectionCount(0)); //Always 1
Console.WriteLine("GC gen 1 collection count = " + GC.CollectionCount(1)); //Always 1
Console.WriteLine("GC gen 2 collection count = " + GC.CollectionCount(2)); //Always 1
}
}
}
Похоже, что все поколения заполняются и проходят GCed.
Если List<T>
использует структуру Enumerator
, то не должно происходить никаких выделений кучи до тех пор, пока не будет вызван GC.Collect()
(после этого при вызовах Console.WriteLine()
создаются строковые объекты, но это происходит после запуска GC).
Вопрос 1. Почему происходит выделение кучи при использовании List<T>
?
Вопрос 2: я понимаю, что будет выделение кучи из-за ссылочного типа Enumerator
, используемого при итерации List<T>
через интерфейс IList<T>
(при условии, что комментарий в связанном вопросе верен), но почему коллекция происходит во всех 3 поколениях?
2000 объектов Enumerator — это множество объектов, но как только цикл foreach завершится, он будет готов к сборке мусора, потому что после завершения foreach ничего не ссылается на объект. Почему объекты доходят до Gen 1 и Gen 2?
IFormattable formattable = 1
. Это не относится к тому, находится ли тип значения внутри другого объекта и на этот объект ссылаются через интерфейс. Следовательно, foreach-обработка List‹int› и IList‹int› должна вести себя одинаково. - person ckuri   schedule 05.06.2021List<T>
имеет перечислитель который является структурой, но если вы обращаетесь к нему какIEnumerable<T>
, он упаковывается - person canton7   schedule 05.06.2021GC.CollectionCount
. Он показывает, сколько раз было собрано это поколение.GC.Collect()
вызывает сбор всех поколений. Итак, все, что вы видите, это то, что сбор произошел, потому что вы позвонилиGC.Collection()
. Вы ничего не видите о том, были ли выделены какие-либо объекты. Используйте BenchmarkDotNet сMemoryDiagnoser
- person canton7   schedule 05.06.2021Console.WriteLine(number);
существенной частью проблемы или это просто ненужное усложнение, которое можно удалить? - person Theodor Zoulias   schedule 05.06.2021