Мои любимые ошибки с IDisposable. 2/3
Начало
3. Проблемы using и yield
IDisposable и using дают нам инструменты для управления очисткой наших ресурсов, но это может противоречить ленивой оценке, которую мы получаем от ключевого слова yield.Давайте возьмём простой пример и воспользуемся
Console.WriteLine, чтобы увидеть, как проявляется ошибка. Нам понадобится реализация IDisposable и несколько вспомогательных функций, чтобы реализовать неявный вызов. Сначала рассмотрим рабочий код:https://dotnetfiddle.net/WwiN0r
Когда мы выполняем этот код, мы получаем ожидаемый результат:
startВсе операции расположены в правильном порядке, и мы очищаем наши ресурсы сразу после использования. Заменим вывод метода
connected
querying
dispose
result: 1
done
Query в строке 17 на yield return:https://dotnetfiddle.net/sfF1Au
Теперь результат довольно странный:
startНаш запрос выполняется ПОСЛЕ очистки ресурсов! Заметьте, что в реальном приложении вы получите ошибку при обращении к несуществующему ресурсу. Что происходит?
connected
dispose
querying
result: 1
done
Мы просто добавили
yield return, но компилятор «под капотом» проделал намного больше работы. Он создал класс с уникальным именем, реализующий IEnumerable<int>. Наш запрос к FakeResource возвращает экземпляр этого класса, но мы не выполняем собственно запроса, пока не начнёт исполняться цикл foreach. У Джона Скита есть отличный разбор всех внутренностей реализации. А подробнее про особенности выражения yield return, вы можете почитать на канале. Поищите посты по строке "Выражение yield return".Главное отличие здесь в том, что мы перешли от немедленного оценивания выражения к ленивому, и это изменило реальный порядок действий: мы покинули блок
using перед запуском нашего кода в Query(FakeResource).Эта ошибка замечательна, потому что
yield return и using могут быть в совершенно разных частях проекта. А чтобы столкнуться с этой ошибкой, нам достаточно хотя бы одного уровня косвенного доступа, и это довольно распространённое явление. При этом изменение на yield return само по себе выглядит невинно, и компилятор всем доволен.Этот вид ошибки также может быть создан с помощью
async и await; мы можем покинуть блок using, пока задача всё ещё хочет использовать наш IDisposable (см. этот пост).Окончание следует…
Источник: https://www.lazy-electron.com/2021/03/06/favorite-idisposable-bugs.html
Автор оригинала - Ryan Davis