Почему IEnumerator ‹T› наследуется от IDisposable, а неуниверсальный IEnumerator - нет?

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

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


person Morgan Cheng    schedule 24.10.2008    source источник


Ответы (6)


В основном это был недосмотр. В C # 1.0 foreach никогда не вызывал Dispose 1. С C # 1.2 (представленный в VS2003 - нет 1.1, как ни странно) foreach начал проверять в блоке finally, реализован ли итератор IDisposable - они должны были сделать это таким образом, потому что ретроспективно выполнение IEnumerator extension 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.

person Jon Skeet    schedule 24.10.2008
comment
должен ли я ожидать, что IEnumerable.GetEnumerator (не общий) тоже будет IDisposable? - person Shimmy Weitzhandler; 08.01.2012
comment
@Shimmy: Код, который принимает произвольные реализации неуниверсального IEnumerable, обязан гарантировать, что любой одноразовый объект, возвращенный из GetEnumerator, будет удален. Код, который этого не делает, следует рассматривать как неработающий. - person supercat; 28.01.2013
comment
@supercat: С момента написания этого ответа я обнаружил, что он уже был в спецификации 1.0. Мне не удалось получить установку 1.0, чтобы проверить, прав ли я на самом деле или нет в отношении поведения. Буду редактировать. - person Jon Skeet; 28.01.2013
comment
@JonSkeet: Любая версия C #, в которой не было Dispose логики в foreach, вела бы себя очень плохо с классом VisualBasic.Collection (который во многих отношениях раздражает и причудлив, но - в отличие от любых других коллекций Microsoft того времени - допускает элементы удаляются при подсчете). Класс Collection избегает хранения каких-либо сильных ссылок на невыполненные перечислители и очищает их, если они собираются сборщиком мусора, но если сборка перечисляется много раз между циклами сборки мусора и эти перечислители не очищаются, он получит очень медленно. - person supercat; 28.01.2013
comment
@supercat: Конечно, это не значит, что этого не было ...;) Поэтому я хочу это проверить. - person Jon Skeet; 28.01.2013

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

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

person Mark Cidade    schedule 24.10.2008
comment
Если компилятор знает IEnumerable при компиляции, поэтому он может оптимизировать вызов Dispose, он также может оптимизировать проверку типа. - person Edward Brey; 09.04.2019

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

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

person JAXN    schedule 28.12.2011
comment
Если GetEnumerator возвращает объект, требующий очистки (например, потому что он читает строки данных из файла, который необходимо закрыть), объект, который знает, когда перечислитель больше не нужен, должен иметь некоторые средства передачи этой информации некоторому объекту, который может выполнить очистку. IDisposable ведет себя в обратном направлении по отношению к принципу замены Лискова, поскольку фабрика, которая возвращает вещи, которые могут требовать очистки, небезопасно заменять фабрикой, которая обещает вернуть вещи, которые этого не делают, но обратная подстановка будет безопасной и должна < / i> быть законным. - person supercat; 28.01.2013
comment
Я также обнаружил, что IDisposable на IEnumerator<T> немного сбивает с толку. Я счел полезным проверить, как _ 3_ из List<T> реализовано IDisposable в исходном коде .NET. Обратите внимание, что в Enumerator struct есть метод Dispose, но в нем ничего нет. Обратите внимание, что такое IDisposable поведение никоим образом не подразумевает, что A List<Bitmap> должен удалить свои Bitmap в foreach! - person jrh; 26.01.2017
comment
Связанный: IEnumerator: нормально ли иметь пустой Dispose метод? (ответ: да, есть) - person jrh; 26.01.2017
comment
@supercat хорошее использование принципа замены Лискова для исправления нарушения принципа разделения интерфейса;) - person mireazma; 26.09.2020
comment
@mireazma: Было бы полезно, если бы типы контейнеров имели немного отличающуюся иерархию от типов объектов-экземпляров, при этом, помимо прочего, контейнеры можно было бы классифицировать в зависимости от того, владеют ли они эксклюзивными ссылками, собственными открытыми ссылками, ссылаются на объекты, принадлежащие другим, или ссылаются на бесхозяйные объекты. То, как коллекция должна реализовывать методы для таких вещей, как проверка равенства, должно зависеть от таких различий, но язык не может передать их. - person supercat; 26.09.2020

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

person Aaron Powell    schedule 24.10.2008
comment
Iisposing? Кроме того, вы имеете в виду Не в соответствии с ...? - person Peter Ritchie; 08.11.2011
comment
Я дал вам +1, чтобы противодействовать -1, поскольку ваше сообщение было опубликовано между тем временем, когда исходный постер неправильно указал IEnumerable и IEnumerator, и, вероятно, именно это побудило OP исправить свой вопрос. Тем не менее, поскольку вопрос уже давно исправлен, вы также можете удалить свой ответ, поскольку он определенно больше не применим. - person supercat; 03.02.2013

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

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

person Bevan    schedule 24.10.2008
comment
yield просто заставляет компилятор генерировать код для конечного автомата, который не требует утилизации за пределами обычного GC - person Mark Cidade; 24.10.2008
comment
@marxidad: Совершенно неверно. Рассмотрим, что произойдет, если в блоке итератора встречается оператор using. См. csharpindepth.com/Articles/Chapter6/. - person Jon Skeet; 24.10.2008
comment
@Jon: Не совсем неверно. Хотя IDisposable не является строго необходимым в случаях, когда использование не используется, проще сделать все перечислители нового стиля одноразовыми и на всякий случай вызывать Dispose () каждый раз. - person Mark Cidade; 24.10.2008
comment
Я собирался перефразировать свой комментарий, чтобы сделать его немного мягче, чем полностью неверным, но утверждение, что компилятор генерирует код, который не требует никакого удаления, все еще неточно ИМО. Это иногда, но ваше общее заявление было неверным. - person Jon Skeet; 24.10.2008
comment
Класс VisualBasic.Collection будет работать плохо (на несколько порядков медленнее, чем обычно), если многие счетчики будут созданы и заброшены без удаления. Таким образом, необходимость ассоциировать Dispose с IEnumerable была очевидна до того, как выпуск .net 1.0, but probably late enough that changing IEnumerable` для наследования IDisposable повлиял бы на график выпуска. - person supercat; 28.01.2013

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

person BCS    schedule 24.10.2008