Подразумевает ли создание объекта типа IEnumerable<T> (например, объекта numbers типа IEnumerable<int> в приведенном ниже примере кода) создание объекта в куче, который в конечном итоге подлежит сборке мусора?
Рассмотрим, например, следующий итератор, описанный в этом сообщении блога
IEnumerable<int> GetOneTwoThree() {
yield return 1;
yield return 2;
yield return 3;
}
а затем его использование с
var numbers = GetOneTwoThree();
foreach (var number in numbers) {
Console.WriteLine(number);
}
yield (и foreach) — это «просто» синтаксический сахар, я рекомендую взглянуть на то, что на самом деле компилируется с помощью такого инструмента, как Sharplab, чтобы получить более глубокое понимание.
какое тебе дело? Какую проблему вы на самом деле пытаетесь решить, отвечая на этот вопрос? Или это скорее академический интерес?
@MakePeaceGreatAgain В конце концов, речь идет о производительности. В определенных частях нашего кода (частях, которые выполняются много раз) мы избегаем выделения кучи, поскольку мы обнаружили, что сборщик мусора замедляет наше приложение, особенно в контексте многопоточности.
ну, вы уж точно не выделяете миллиарды итераторов, не так ли? Так что разница, если она вообще есть, вероятно, незначительна. Однако только вы можете измерить.
^^ и тест может показать эффект, если вам абсолютно необходимо это знать (или доказать начальству).
Хорошо, нам следует провести тест. Тем не менее, меня все еще интересует ответ.
Думаю, вы получили ответ в самом первом комментарии, не так ли?
Другими словами: хотя можно выполнять итерацию без выделения памяти, использование IEnumerable<T> маловероятно.
@Филдор, ты поэт, хорошая рифма.
Состояние, требующее @Raildex, не требует выделения кучи, поэтому вопрос очень правильный (пример Sharplab следует); как деталь реализации, это тот случай, когда компилятор в настоящее время не поддерживает написание пользовательских итераторов как struct при использовании yield return
@MakePeaceGreatAgain Это было непреднамеренно, хотя я должен признать.





подразумевать создание объекта в куче, который, наконец, должен быть собран мусором?
Краткий ответ: Да, в этом случае
Помимо всего остального: в тот момент, когда вы реализуете IEnumerable<T>, это означает, что ваш итератор типизирован как IEnumerator<T>, поэтому даже если он на самом деле был реализован в типе значения: он будет упакован (таким образом: выделение кучи) при вводе как интерфейс.
Более длинный ответ: можно написать рукописные итераторы, использующие struct (или даже ref struct), которые затем можно использовать без какого-либо выделения кучи - при условии, что тип используется явно, а не через интерфейсы IEnumerable<T>/IEnumerator<T> - List<T> является пример этого, с пользовательским итератором типа значения. Однако это исключает использование yield return — он должен быть написан от руки. К счастью, foreach имеет утиный тип, поэтому можно эффективно использовать такие пользовательские итераторы (когда метод GetEnumerator() возвращает что-то специальное, имеющее необходимые функции).
Разве коробочный IEnumerator<T> не может быть выделен в стеке?
@OlivierJacot-Descombes Мне не известен ни один сценарий, в котором среда выполнения и/или JIT использует стек для операций упаковки (а компилятор, конечно, этого не делает), и необходимый для этого escape-анализ был бы значительным - рад быть исправленным, хотя
@OlivierJacot-Descombes См. github.com/dotnet/runtime/issues/11192 и github.com/dotnet/runtime/pull/103361 это приближается, но это огромный объем работы.
Состояние метода (где был последний выполненный
yield) должно где-то храниться. И это точно не в стеке