Почему IEnumerator<T> наследуется от IDisposable, а неуниверсальный IEnumerator — нет?
-
04-07-2019 - |
Вопрос
Я заметил, что общий IEnumerator<T>
наследует от IDisposable, а неуниверсальный интерфейс IEnumerator — нет.Почему он спроектирован таким образом?
Обычно мы используем оператор foreach для прохождения IEnumerator<T>
пример.Сгенерированный код foreach на самом деле имеет блок try-finally, который вызывает Dispose() наконец.
Решение
В основном это был недосмотр. В C # 1.0 foreach
never называется 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
.
РЕДАКТИРОВАТЬ: невероятно полезно вызывать Dispose
в конце итерации (как бы он ни заканчивался). Это означает, что итератор может удерживать ресурсы - что делает возможным, скажем, чтение файла построчно. Блоки итераторов генерируют реализации Dispose
, которые гарантируют, что любые блоки finally
, относящиеся к " текущей точке выполнения " итератора выполняются, когда он расположен - так что вы можете написать нормальный код внутри итератора, и очистка должна происходить соответствующим образом.
1 Возвращаясь к спецификации 1.0, она уже была указана. Я еще не смог проверить это более раннее утверждение, что реализация 1.0 не вызывала Dispose
.
Другие советы
IEnumerable<T> не наследует IDisposable.Однако IEnumerator<T> наследует IDisposable, тогда как неуниверсальный IEnumerator — нет.Даже когда вы используете для каждого для необщего IEnumerable (который возвращает IEnumerator) компилятор все равно сгенерирует проверку IDisposable и вызовет Dispose(), если перечислитель реализует интерфейс.
Я предполагаю, что общий Enumerator<T> наследует от IDisposable, поэтому нет необходимости проводить проверку типа во время выполнения — он может просто пойти дальше и вызвать Dispose(), который должен иметь лучшую производительность, поскольку его, вероятно, можно оптимизировать, если перечислитель имеет пустой метод Dispose().
Я знаю, что это старая дискуссия, но я заново написал библиотеку, в которой я использовал IEnumerable из T / IEnumerator из T, где пользователи библиотеки могли бы реализовывать пользовательские итераторы, им просто нужно реализовывать IEnumerator из T.
Мне показалось очень странным, что IEnumerator из T наследовал бы от IDisposable. Мы реализуем IDisposable, если хотим освободить неуправляемые ресурсы, верно? Таким образом, это будет актуально только для перечислителей, которые на самом деле содержат неуправляемые ресурсы - например, поток ввода-вывода и т. Д. Почему бы просто не позволить пользователям реализовать IEnumerator из T и IDisposable на своем перечислителе, если это имеет смысл? В моей книге это нарушает принцип единственной ответственности - зачем смешивать счетную логику и уничтожать объекты. Р>
IEnumerable` наследует IDisposing? Согласно отражателю .NET или MSDN . Вы уверены, что не путаете это с IEnumerator ? При этом используется IDisposing, потому что он предназначен только для перечисления коллекции и не предназначен для долговечности.
Немного трудно быть в этом определенным, если только вам не удастся получить ответ от самого AndersH или кого-то из его близких.
Однако я предполагаю, что это относится к " yield " Ключевое слово, которое было введено в C # одновременно. Если вы посмотрите на код, сгенерированный компилятором, когда "yield return x" используется, вы увидите метод, заключенный во вспомогательный класс, который реализует IEnumerator; IEnumerator наследуется от IDisposable, что гарантирует его очистку после завершения перечисления.
IIRC Все, что связано с IEnumerable < T >
и IEnumerable
, является результатом того, что IEnumerable
предшествовал материалу шаблона .Net. Я подозреваю, что ваш вопрос такой же.