Использование yield return для описания итератора - PullRequest
0 голосов
/ 04 ноября 2019

Я был бы очень признателен, если бы кто-то проверил мои мысли по этой теме и исправил их там, где они не правы. Я надеюсь, что этот тип «пожалуйста, проверьте мои мысли» не слишком сильно противоречит правилам сайта, и мой вопрос - скорее попытка ответить на вопрос, как работает определенная тема, от человека, который на самом деле не понимаеттема. Надеюсь, это может быть полезным / проницательным для кого-то, кто является новичком, как я, и, возможно, захочет увидеть общее описание происходящего на высоком уровне.

Вот мой подход к пониманию темы 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 в теле, тогда он создаетвспомогательный объект и т. д., как я уже упоминал, поэтому я не уверен, сколько еще синтаксического сахара может быть, кроме этого.

...