Подразумевает ли использование итератора объект кучи?

Подразумевает ли создание объекта типа 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) должно где-то храниться. И это точно не в стеке

Raildex 07.08.2024 13:04
yield (и foreach) — это «просто» синтаксический сахар, я рекомендую взглянуть на то, что на самом деле компилируется с помощью такого инструмента, как Sharplab, чтобы получить более глубокое понимание.
MindSwipe 07.08.2024 13:20

какое тебе дело? Какую проблему вы на самом деле пытаетесь решить, отвечая на этот вопрос? Или это скорее академический интерес?

MakePeaceGreatAgain 07.08.2024 13:30

@MakePeaceGreatAgain В конце концов, речь идет о производительности. В определенных частях нашего кода (частях, которые выполняются много раз) мы избегаем выделения кучи, поскольку мы обнаружили, что сборщик мусора замедляет наше приложение, особенно в контексте многопоточности.

Amos Egel 07.08.2024 13:33

ну, вы уж точно не выделяете миллиарды итераторов, не так ли? Так что разница, если она вообще есть, вероятно, незначительна. Однако только вы можете измерить.

MakePeaceGreatAgain 07.08.2024 13:35

^^ и тест может показать эффект, если вам абсолютно необходимо это знать (или доказать начальству).

Fildor 07.08.2024 13:37

Хорошо, нам следует провести тест. Тем не менее, меня все еще интересует ответ.

Amos Egel 07.08.2024 13:38

Думаю, вы получили ответ в самом первом комментарии, не так ли?

MakePeaceGreatAgain 07.08.2024 13:43

Другими словами: хотя можно выполнять итерацию без выделения памяти, использование IEnumerable<T> маловероятно.

Fildor 07.08.2024 13:45

@Филдор, ты поэт, хорошая рифма.

MakePeaceGreatAgain 07.08.2024 13:47

Состояние, требующее @Raildex, не требует выделения кучи, поэтому вопрос очень правильный (пример Sharplab следует); как деталь реализации, это тот случай, когда компилятор в настоящее время не поддерживает написание пользовательских итераторов как struct при использовании yield return

Marc Gravell 07.08.2024 14:08
Sharplab.io/…
Marc Gravell 07.08.2024 14:08

@MakePeaceGreatAgain Это было непреднамеренно, хотя я должен признать.

Fildor 07.08.2024 14:16
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
13
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

подразумевать создание объекта в куче, который, наконец, должен быть собран мусором?

Краткий ответ: Да, в этом случае

Помимо всего остального: в тот момент, когда вы реализуете IEnumerable<T>, это означает, что ваш итератор типизирован как IEnumerator<T>, поэтому даже если он на самом деле был реализован в типе значения: он будет упакован (таким образом: выделение кучи) при вводе как интерфейс.


Более длинный ответ: можно написать рукописные итераторы, использующие struct (или даже ref struct), которые затем можно использовать без какого-либо выделения кучи - при условии, что тип используется явно, а не через интерфейсы IEnumerable<T>/IEnumerator<T> - List<T> является пример этого, с пользовательским итератором типа значения. Однако это исключает использование yield return — он должен быть написан от руки. К счастью, foreach имеет утиный тип, поэтому можно эффективно использовать такие пользовательские итераторы (когда метод GetEnumerator() возвращает что-то специальное, имеющее необходимые функции).

Разве коробочный IEnumerator<T> не может быть выделен в стеке?

Olivier Jacot-Descombes 07.08.2024 14:02

@OlivierJacot-Descombes Мне не известен ни один сценарий, в котором среда выполнения и/или JIT использует стек для операций упаковки (а компилятор, конечно, этого не делает), и необходимый для этого escape-анализ был бы значительным - рад быть исправленным, хотя

Marc Gravell 07.08.2024 14:12

@OlivierJacot-Descombes См. github.com/dotnet/runtime/issues/11192 и github.com/dotnet/runtime/pull/103361 это приближается, но это огромный объем работы.

Charlieface 07.08.2024 15:15

Другие вопросы по теме