Чому IEnumerator <T> успадковує від IDisposable, а незагальний IEnumerator ні?


78

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

Зазвичай ми використовуємо оператор foreach для перегляду IEnumerator<T>екземпляра. Сформований код foreach насправді має блок try-нарешті, який викликає Dispose () у нарешті.

Відповіді:


84

В основному це був недогляд. У 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так що вам не потрібна перевірка часу виконання в блоці нарешті, і тепер компілятор знає, що якщо ітератор є, IEnumerator<T>він може видавати безумовний виклик Dispose.

РЕДАГУВАТИ: Це неймовірно корисно для того, Disposeщоб його викликали в кінці ітерації (однак вона закінчується). Це означає, що ітератор може триматися на ресурсах - що робить можливим, наприклад, читати файл, рядок за рядком. Блоки ітератора генерують Disposeреалізації, які гарантують, що будь-які finallyблоки, що мають відношення до "поточної точки виконання" ітератора, виконуються, коли він утилізується - так що ви можете написати звичайний код в ітераторі, і очищення має відбутися належним чином.


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


чи повинен я очікувати, що IEnumerable.GetEnumerator(не-загальний) буде IDisposableтаким же?
Shimmy Weitzhandler

1
@Shimmy: Код, який приймає довільні реалізації не-загальних IEnumerable, зобов'язаний забезпечити GetEnumeratorутилізацію будь-якого одноразового об'єкта, з якого повернуто . Кодекс, який не застосовується, слід вважати порушеним.
supercat

@supercat: Після написання цієї відповіді я виявив, що це вже було в специфікації 1.0. Мені не вдалося зачепити інсталяцію 1.0, щоб перевірити, чи я насправді мав рацію щодо поведінки. Буде редагувати.
Джон Скіт

1
@JonSkeet: Будь-яка версія C #, в якій не було Disposeлогіки foreach, поводилась би дуже погано з VisualBasic.Collectionкласом (що багато в чому дратує і вигадливо, але - на відміну від будь-якої іншої колекції Microsoft тієї епохи - дозволяє видаляти елементи під час перерахування). Collection уникає класу проведення якої - або сильних посилання на видатні переписувач, і очистять їх , якщо вони отримують збирач сміття, але якщо збір перераховується багато разів між GC циклами і тими лічильниками не очищені, він буде отримувати дуже повільно.
supercat

@supercat: Звичайно, це не означає, що цього не сталося ...;) Ось чому я хочу це перевірити.
Джон Скіт,

5

IEnumerable <T> не успадковує IDisposable. Однак IEnumerator <T> успадковує IDisposable, тоді як не загальний IEnumerator - ні. Навіть коли ви використовуєте foreach для не-загального IEnumerable (який повертає IEnumerator), компілятор все одно генерує перевірку на наявність IDisposable і викликає Dispose (), якщо перечислювач реалізує інтерфейс.

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


Якщо компілятор знає IEnumerableкомпіляцію настільки, що може оптимізувати виклик до Dispose, він також може оптимізувати перевірку типу.
Едвард Брей,

5

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

Мені було дуже дивно, що IEnumerator T успадкує від IDisposable. Ми застосовуємо IDisposable, якщо хочемо звільнити некеровані ресурси, правда? Тож це було б доречним лише для перелічувачів, які насправді містять некеровані ресурси - наприклад, потік вводу-виводу тощо. Чому б просто не дозволити користувачам реалізувати на своєму перечислювачі як IEnumerator T, так і IDisposable, якщо це має сенс? У моїй книзі це порушує принцип єдиної відповідальності - навіщо поєднувати логіку перерахування та розпорядження предметами.


1
Якщо GetEnumeratorповертає об’єкт, який вимагає очищення (наприклад, тому що це зчитування рядків даних із файлу, який доведеться закрити), сутність, яка знає, коли перелічувач більше не потрібен, повинна мати певні засоби передачі цієї інформації деякій сутності, яка може виконати зачистку. IDisposableповодиться відстало щодо Принципу заміщення Ліскова, оскільки завод, який повертає речі, які можуть вимагати очищення, не можна безпечно замінити тим, хто обіцяє повернути речі, які цього не роблять, але зворотне заміщення було б безпечним і повинно бути законним.
supercat

Я також знайшов IDisposableна IEnumerator<T>бути трохи заплутаним, я знайшов , що це корисно , щоб перевірити, як Enumeratorз List<T>реалізованоIDisposable в джерелі .NET. Зверніть увагу, як метод Enumerator structмає Disposeметод, але в ньому немає нічого. Зверніть увагу, що така IDisposableповедінка жодним чином не передбачає щось на кшталт "А List<Bitmap>слід розпоряджатися своїми Bitmaps foreach! "
jrh

Пов’язане: IEnumerator: Чи нормально мати порожній метод Dispose? (відповідь: так, це так)
jrh

@supercat вдале використання Принципу заміщення Ліскова для виправлення порушення принципу сегрегації інтерфейсу;)
mireazma

@mireazma: Було б корисно, якби типи контейнерів мали дещо іншу ієрархію від типів екземплярів об’єктів, серед іншого класифікація контейнерів відповідно до того, чи є вони ексклюзивними посиланнями, власними відкритими посиланнями, посилаються на об’єкти, що належать іншим, або посилаються на безхазяйні предмети. Те, як колекція повинна реалізовувати методи для таких речей, як перевірка рівності, має залежати від таких відмінностей, але мова не має можливості передати їх.
supercat

0

Чи IEnumerable `успадковує IDisposition? Відповідно до рефлектора .NET або MSDN . Ви впевнені, що не плутаєте це з IEnumerator ? Тут використовується IDisposition, оскільки він призначений лише для перерахування колекції, а не для довголіття.


Ідентифікує? Крім того, ви маєте на увазі "Не згідно ..."?
Пітер Річі,

2
Я дав вам +1, щоб протидіяти -1, оскільки ваше повідомлення було розміщене між тим часом, коли оригінальний плакат неправильно вказав IEnumerable, та IEnumerator, і, ймовірно, це спонукало ОП виправити своє запитання. Тим не менше, оскільки питання вже давно було вирішено, ви можете також видалити свою "відповідь", оскільки вона, безумовно, вже не застосовується.
supercat 02

0

Трохи важко визначитися з цим, якщо вам не вдасться отримати відповідь від самого Андерша або когось із його близьких.

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


yield просто змушує компілятор генерувати код для автомата стану, який не потребує утилізації поза звичайним GC
Mark Cidade

@marxidad: Цілком неправильно. Поміркуйте, що відбувається, якщо в блоці ітератора відбувається оператор "using". Дивіться csharpindepth.com/Articles/Chapter6/…
Джон Скіт,

@Jon: Не зовсім неправильно. Хоча IDisposable не є суворо необхідним у випадках, коли використання не використовується, простіше просто зробити всі перелічувачі нового стилю одноразовими і щоразу викликати Dispose () про всяк випадок.
Марк Сідаде

Я збирався переформулювати свій коментар, щоб зробити його трохи м'якшим, ніж "wholel неправильний", але стверджуючи, що компілятор генерує код, який не потребує утилізації, все ще є неточним IMO. Це іноді робить, але ваше ковдру заяву було неправильно.
Джон Скіт,

VisualBasic.CollectionКлас буде виконувати погано ( на кілька порядків повільніше , ніж зазвичай) , якщо багато переписувачі створені і занедбані , не будучи розташоване. Таким чином, необхідність спілкування Disposeз IEnumerableочевидною була до виходу .net 1.0, but probably late enough that changing IEnumerable` для успадкування IDisposableвплинула б на графік випуску.
supercat

-2

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.