Почему IEnumerator <T> наследует от IDisposable, а не общий IEnumerator - нет?

Я заметил, что общий IEnumerator<T> наследует от IDisposable, но не общий интерфейс IEnumerator этого не делает. Почему он сконструирован таким образом?

Обычно мы используем оператор foreach для прохождения экземпляра IEnumerator<T>. Сгенерированный код foreach на самом деле имеет блок try-finally, который в конечном итоге вызывает Dispose().

Ответ 1

В основном это был недосмотр. В С# 1.0 foreach никогда не вызывал Dispose 1. С С# 1.2 (введенный в VS2003 - там нет 1.1, странно) foreach начал проверять блок finally, был ли реализован итератор IDisposable - они должны были сделать это таким образом, потому что ретроспективно создание IEnumerator расширение IDisposable сломало бы все реализации IEnumerator. Если бы они исходили из того, что полезно foreach утилизировать итераторы в первую очередь, я уверен, что IEnumerator расширил бы IDisposable.

Когда появились С# 2.0 и .NET 2.0, у них появилась новая возможность - новый интерфейс, новое наследование. Намного больше смысла расширять интерфейс IDisposable, так что вам не нужна проверка времени выполнения в блоке finally, и теперь компилятор знает, что если итератор является IEnumerator<T>, он может испускать безусловный вызов до Dispose.

EDIT: он невероятно полезен для Dispose для вызова в конце итерации (однако он заканчивается). Это означает, что итератор может ухватиться за ресурсы, что делает его возможным, скажем, читать файл за строкой. Итератор блокирует генератор Dispose, который гарантирует, что любые блоки finally, относящиеся к "текущей точке выполнения" итератора, выполняются при его размещении, поэтому вы можете записать нормальный код в итераторе, и очистка должна произойти соответствующим образом.


1 Оглядываясь на спецификацию 1.0, он уже был указан. Я еще не смог проверить этот предыдущий оператор, что реализация 1.0 не вызывала Dispose.

Ответ 2

IEnumerable <T> не наследует IDisposable. Однако IEnumerator <T> наследует IDisposable, тогда как нетривиальный IEnumerator этого не делает. Даже если вы используете foreach для не-generic IEnumerable (который возвращает IEnumerator), компилятор все равно будет генерировать проверку IDisposable и вызывать Dispose(), если перечислитель реализует интерфейс.

Я предполагаю, что общий Enumerator <T> наследует от IDisposable, поэтому нет необходимости выполнять проверку типа времени выполнения, он может просто пойти и вызвать Dispose(), который должен иметь лучшую производительность, поскольку он может быть, вероятно, оптимизирован если перечислитель имеет пустой метод Dispose().

Ответ 3

Я знаю, что это старая дискуссия, но я разумно написал библиотеку, где я использовал IEnumerable из T/IEnumerator of T, где пользователи библиотеки могли реализовать пользовательские итераторы, они должны просто реализовать IEnumerator из T.

Мне было очень странно, что IEnumerator of T наследует от IDisposable. Мы реализуем IDisposable, если хотим освободить неиспользованные ресурсы? Таким образом, для счетчиков действительно было бы действительно иметь неуправляемые ресурсы - например, поток ввода-вывода и т.д. Почему бы просто не позволить пользователям реализовать IEnumerator из T и IDisposable в своем перечислителе, если это имеет смысл? В моей книге это нарушает принцип единой ответственности - зачем смешивать логику перечислителя и утилизировать объекты.

Ответ 4

Является ли IEnumerable `inherit IDisposing? В соответствии с рефлектором .NET или MSDN. Вы уверены, что не смешиваете его с IEnumerator? Это использует IDisposing, потому что он предназначен только для перечисления коллекции и не предназначен для долговечности.

Ответ 5

Немного трудно быть окончательным на этом, если вам не удастся получить ответ от самого AndersH или кого-то рядом с ним.

Однако я предполагаю, что он относится к ключевому слову "yield", которое было введено одновременно на С#. Если вы посмотрите на код, сгенерированный компилятором, когда используется "yield return x", вы увидите метод, завершенный в вспомогательном классе, который реализует IEnumerator; спуск IEnumerator из IDisposable гарантирует, что он может очиститься, когда перечисление завершено.

Ответ 6

IIRC. Все дело в том, что IEnumerable<T> и IEnumerable является результатом IEnumerable, предшествующего материалу шаблона .Net. Я подозреваю, что ваш вопрос аналогичен.