Почему 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. Я подозреваю, что ваш вопрос аналогичен.