Я был бы очень признателен, если бы кто-то проверил мои мысли по этой теме и исправил их там, где они не правы. Я надеюсь, что этот тип «пожалуйста, проверьте мои мысли» не слишком сильно противоречит правилам сайта, и мой вопрос - скорее попытка ответить на вопрос, как работает определенная тема, от человека, который на самом деле не понимаеттема. Надеюсь, это может быть полезным / проницательным для кого-то, кто является новичком, как я, и, возможно, захочет увидеть общее описание происходящего на высоком уровне.
Вот мой подход к пониманию темы IEnumerable
, IEnumerator
и yield return.
Способ взаимодействия IEnumerable
и IEnumerator
вполне понятен и естественен - у нас есть два объекта, один из которых содержит коллекцию, а другой - курсор.
На данный момент давайте проигнорируем связь между возвращаемым доходом и двумя интерфейсами и просто рассмотрим следующую концепцию / проблему - предположим, у нас есть код, который имеет выражение / вычисление некоторого целого числа в каждой строке, и мы хотели бысоздайте функцию f
, которая будет выполнять и возвращать эти целые числа последовательно, то есть при первом вызове f()
будет возвращено целое число, вычисленное в первой строке, при втором вызове мы получим второе целое число,и т.д. Естественный способ сделать это - создать некоторый вспомогательный класс / объект, который будет содержатьон упомянул код, но в дополнение к этому он также будет хранить положение, в котором мы находимся в коде. Используя goto
, это можно сделать. Чтобы добиться этого, мы назначаем ключевое слово yield return, чтобы сделать это - компилятор распознает, что если у нас есть какой-то блок кода с возвращением yield внутри, он создаст для него класс помощника / контроллера. Обратите внимание, что то, как этот вспомогательный класс действует на блок кода, уже очень похоже на то, как IEnumerator
действует на IEnumerable
- он хранит «где» мы в некотором смысле.
Далее мы будем использовать этоКонцепция и определить его так, чтобы он соответствовал существующим функциональным возможностям интерфейсов IEnumerator
и IEnumerable
. Основная функциональность, к которой мы будем стремиться, - это foreach
- при использовании с объектом, который реализует интерфейс IEnumerable
, он возвращает курсор, реализованный в IEnumerator
через IEnumerable.GetEnumerator
, а затем использует методы MoveNext
и current
, чтобы перемещаться по коллекции и возвращать объект, указанный соответственно.
Итак, первый способ, которым мы могли бы использовать эту концепцию, это определить только GetEnumerator
- мы просто помещаем блок кода, который мыхочу быть последовательно выполненным в теле GetEnumerator
. Чтобы это работало, компилятор распознает, что если тело GetEnumerator
содержит yield return
, он создаст вспомогательный объект (и в конце вернет этот объект к вызову GetEnumerator
), и этот помощникКласс также реализует методы MoveNext
и Current
как "выполнить следующую строку и сохранить в _current
(некоторое поле вспомогательного класса) и" return _current
"соответственно. По существу, GetEnumerator
вернетвспомогательный объект упоминал два абзаца выше, и, кроме того, компилятор создаст необходимые MoveNext
и Current
методы в этом вспомогательном объекте, чтобы они, перемещаясь вдоль блока кода, возвращали значение соответственно.
Другой способ, которым мы могли бы использовать эту концепцию, - это напрямую «определить» коллекцию и пройти через нее. Поэтому на этот раз мы не просто возвращаем класс IEnumerator
/ helper, а возвращаем IEnumerable
Но для наших целей мы на самом деле просто заинтересованы в возможности использовать этот объект с foreach
. Поэтому один из способов, которым мы можем это сделать, это просто сделать почти то же самое,s выше - мы реализуем вспомогательный объект как IEnumerable
, а также IEnumerator
- так что мы можем вернуть его как IEnumerable
в некотором операторе foreach
, и мы реализуем GetEnumerator
, чтобы просто вернуть this
,Так что эффект очень похож на описанный выше.
Имеет ли смысл вышесказанное? Одна вещь, которая мне не ясна, заключается в том, можно ли, например, foreach
как-то использовать непосредственно с IEnumerator
- то есть можно ли сделать (foreach element in Enumerator())
, где Enumerator()
- это функция, которая возвращает только IEnumerator
, а неIEnumerable
. Это не имеет смысла, но я не уверен, что компилятор не может разобраться в этих вещах - тем более, что он способен сдерживать такие радикально разные вещи, если он видит yield return
в теле, тогда он создаетвспомогательный объект и т. д., как я уже упоминал, поэтому я не уверен, сколько еще синтаксического сахара может быть, кроме этого.