Поделиться через


Расширение поддержки GetEnumerator для циклов foreach.

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она содержит предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия фиксируются в соответствующих совещаниях по проектированию языка (LDM) .

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Проблема чемпиона: https://github.com/dotnet/csharplang/issues/3194

Сводка

Разрешите циклам foreach распознавать метод расширения GetEnumerator, который иначе соответствует шаблону foreach, и выполняться по выражению, если в противном случае это вызовет ошибку.

Мотивация

Это приведет foreach в соответствие с реализацией других функций в C#, включая асинхронный и деконструкцию на основе шаблонов.

Подробный дизайн

Изменение спецификации относительно просто. Мы изменим раздел The foreach statement§13.9.5 на этот текст:

Обработка инструкции foreach во время компиляции сначала определяет тип коллекции , тип перечислителя и тип элемента выражения. Это определение осуществляется следующим образом:

  • Если тип выражения является типом массива, то существует неявное преобразование ссылок из в интерфейс (так как реализует этот интерфейс). Тип коллекции является интерфейсом IEnumerable, тип перечислителя является интерфейсом IEnumerator, а тип элемента является типом элемента типа массива X.

  • Если тип X выражения dynamic то есть неявное преобразование из выражения в интерфейс IEnumerable (§10.2.10). Коллекция типа является интерфейсом IEnumerable, а перечислитель типа является интерфейсом IEnumerator. Если идентификатор var задан как тип локальной переменной, является dynamic, в противном случае - object.

  • В противном случае определите, имеет ли тип X соответствующий метод GetEnumerator:

    • Выполните поиск члена для типа X с идентификатором GetEnumerator и без аргументов типа. Если поиск элемента не создает совпадение или создает неоднозначность или создает совпадение, которое не является группой методов, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется выдать предупреждение, если поиск элемента создает что-либо, кроме группы методов или отсутствия совпадения.
    • Выполните разрешение перегрузки с помощью результирующей группы методов и пустого списка аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, проверьте наличие перечисленного интерфейса, как описано ниже. Рекомендуется выдавать предупреждение, если разрешение перегрузки не приводит к однозначному общедоступному методу экземпляра или не найдено применимых методов.
    • Если возвращаемый тип E метода GetEnumerator не является классом, структурой или типом интерфейса, создается ошибка и дальнейшие действия не выполняются.
    • Выполняется поиск элемента на E с идентификатором Current без аргументов типа. Если поиск элемента не соответствует, результатом является ошибка, или результатом является что-либо, кроме свойства общедоступного экземпляра, которое разрешает чтение, создается ошибка, и дальнейшие действия не выполняются.
    • Выполняется поиск элемента на E с идентификатором MoveNext без аргументов типа. Если в результате поиска участника не находится совпадение, или если результат является чем-либо, кроме группы методов, возникает ошибка, и дальнейшие шаги не предпринимаются.
    • Разрешение перегрузки выполняется в группе методов с пустым списком аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, или его тип возвращаемого значения не bool, возникает ошибка и дальнейшие шаги не выполняются.
    • Тип коллекции равен X, тип перечислителя равен E, и тип элемента является типом свойства Current.
  • В противном случае проверьте наличие перечисленного интерфейса:

    • Если среди всех типов , для которых существует неявное преобразование из в , существует уникальный тип таким образом, что не и для всех остальных существует неявное преобразование из в , то тип коллекции является интерфейсом . Тип перечислителя — это интерфейса, а типа элемента .
    • В противном случае, если существует несколько таких Tтипа, возникает ошибка и дальнейшие действия не выполняются.
    • В противном случае, если существует неявное преобразование из X в интерфейс System.Collections.IEnumerable, то тип коллекции является этим интерфейсом, тип перечислителя является интерфейсом System.Collections.IEnumerator, а тип элемента является object.
  • В противном случае определите, имеет ли тип X соответствующий метод расширения GetEnumerator:

    • Выполните поиск метода расширения для типа X с идентификатором GetEnumerator. Если поиск элемента не создает совпадение или создает неоднозначность или создает совпадение, которое не является группой методов, создается ошибка, и дальнейшие действия не выполняются. Рекомендуется выдавать предупреждение, если поиск элемента выдает что-либо, кроме группы методов или отсутствия совпадения.
    • Выполните разрешение перегрузки с помощью результирующей группы методов и одного аргумента типа X. Если разрешение перегрузки не создает применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод недоступен, возникает ошибка, и никакие дальнейшие действия не предпринимаются.
      • Это разрешение позволяет передавать первый аргумент по ссылке, если X является типом структуры, а вид ссылки - in.
    • Если возвращаемый тип E метода GetEnumerator не является классом, структурой или типом интерфейса, создается ошибка и дальнейшие действия не выполняются.
    • Выполняется поиск элемента на E с идентификатором Current без аргументов типа. Если поиск элемента не соответствует, результатом является ошибка, или результатом является что-либо, кроме свойства общедоступного экземпляра, которое разрешает чтение, создается ошибка, и дальнейшие действия не выполняются.
    • Выполняется поиск элемента на E с идентификатором MoveNext без аргументов типа. Если в результате поиска участника не находится совпадение, или если результат является чем-либо, кроме группы методов, возникает ошибка, и дальнейшие шаги не предпринимаются.
    • Разрешение перегрузки выполняется в группе методов с пустым списком аргументов. Если разрешение перегрузки не приводит к применению применимых методов, приводит к неоднозначности или приводит к одному лучшему методу, но этот метод является статическим или не общедоступным, или его тип возвращаемого значения не bool, возникает ошибка и дальнейшие шаги не выполняются.
    • Тип коллекции равен X, тип перечислителя равен E, и тип элемента является типом свойства Current.
  • В противном случае возникает ошибка, и дальнейшие действия не выполняются.

Для await foreachправила изменяются аналогичным образом. Единственное изменение, необходимое для этой спецификации, заключается в удалении строки Extension methods do not contribute. из описания, так как остальная часть этой спецификации основана на приведенных выше правилах с различными именами, замененными методами шаблона.

Недостатки

Каждое изменение добавляет языку дополнительную сложность, и это потенциально допускает ситуации, когда вещи, которые не предназначены для foreachбыть foreach, например, Range.

Альтернативы

Ничего не делая.

Неразрешенные вопросы

Нет на этом этапе.