Почему IEnumerator <T>наследуется от IDisposable, а неуниверсальный IEnumerator - нет? - PullRequest
69 голосов
/ 24 октября 2008

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

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

Ответы [ 6 ]

78 голосов
/ 24 октября 2008

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

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

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


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

5 голосов
/ 24 октября 2008

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

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

4 голосов
/ 28 декабря 2011

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

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

0 голосов
/ 24 октября 2008

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

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

0 голосов
/ 24 октября 2008

IEnumerable` наследует IDisposing? Согласно отражателю .NET или MSDN . Вы уверены, что не путаете это с IEnumerator ? При этом используется IDisposing, потому что он предназначен только для перечисления коллекции и не предназначен для долговечности.

0 голосов
/ 24 октября 2008

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

...