Мои любимые ошибки с IDisposable. 1/3
System.IDisposable - фундаментальный интерфейс, используемый в большинстве программ .NET. Его основная цель - предоставить механизм для высвобождения «неуправляемых» ресурсов: файловых потоков, подключений к базам данных, сетевых сокетов и т.д.public interface IDisposable {
void Dispose();
}
Что может быть проще? Кроме того, для него есть специальный синтаксис - оператор using:using(var stream=File.OpenText("…")) {
// неуправляемый ресурс stream
// живёт внутри этого блока
}
// здесь stream удаляется
Красиво и просто!Но несмотря на эту простоту, удивительно легко выстрелить себе в ногу, неверно используя
IDisposable. Вот мои любимые ошибки.1. IDisposable вам врёт
Кажется, интерфейс легко реализовать: всего один метод
void, и готово! Но IDisposable обманывает. К нему предъявляются некоторые требования, которые нельзя выразить посредством простого объявления интерфейса:- взаимодействие со сборщиком мусора через финализаторы,
- взаимодействие с подклассами,
- обработка повторных вызовов
Dispose.Для решения всех этих проблем мы используем паттерн Dispose. Это настолько распространенная проблема, что IDE и статические анализаторы могут подсказывать вам, как правильно реализовать
IDisposable.В этом отношении
IDisposable нарушает некоторые из наших фундаментальных предположений о полноте интерфейсов C#: недостаточно, чтобы компилятор был доволен, мы должны читать документацию, следить за подсказками статических анализаторов и удовлетворять любые их требования, которые компилятор не сможет проверить.К сожалению, это обычное дело. Например:
- В
IEquatable<T>, вы также должны переопределить базовые реализации Equals(Object) и GetHashCode().- В
IComparable<T>, вы должны перегрузить op_GreaterThan, op_GreaterThanOrEqual и т.п.- Класс, реализующий
IFormattable, должен поддерживать формат «G» (общий)…Библиотеки базовых классов изобилуют подобными хитростями! Обязательно используйте какие-нибудь анализаторы, чтобы они помогали вам с этим.
2. Разработчики IDisposable могут вам врать
Мы уже знаем, что нам нужно использовать шаблон Dispose для наших реализаций
IDisposable. А что насчёт использования чужих реализаций? Например, правильно ли SqlConnection использует шаблон Dispose? У нас нет другого способа узнать это, кроме как читать документацию.Самый большой нарушитель, с которым я столкнулся, - это
HttpClient. Обычно мы хотим освободить наши неуправляемые ресурсы как можно скорее. Реализуя IDisposable, HttpClient посылает нам сигнал о том, что он хочет находиться в блоке using:using(var client = new HttpClient()) {
// выполняем запрос
}
Но использование HttpClient таким образом всё испортит. В документации мы находим следующий перл:HttpClient предназначен для однократного создания и повторного использования в течение всего жизненного цикла приложения. Создание экземпляра класса HttpClient для каждого запроса при больших нагрузках исчерпает количество доступных сокетов.
Это ошибка замечательна тем, что всё отлично работает при разработке и ручном тестировании, но падает даже при небольшой нагрузке. Использование IHttpClientFactory - хорошее решение этой проблемы.
Продолжение следует…
Источник: https://www.lazy-electron.com/2021/03/06/favorite-idisposable-bugs.html
Автор оригинала - Ryan Davis