Должен ли IDisposable применяться каскадно?
Это довольно простой вопрос, однако я все еще немного борюсь с ним.
IDisposable реализуется, когда вы хотите, чтобы пользователь объекта освобождал базовые ресурсы (например, сокеты и т.д.) до того, как объект в конечном итоге собрал мусор.
Когда у меня есть класс, который содержит DbConnection (реализует IDisposable), должен ли мой класс также реализовать IDisposable и связать вызов с DbConnection или любыми другими объектами IDisposable, которыми он владеет? Иначе ресурсы DbConnections будут освобождены только тогда, когда мой класс GarbageCollected, тем самым опустив ссылку на соединение, и GC завершит DbConnection.
Ответы
Ответ 1
Да, вы ВСЕГДА реализуете IDisposable, если вы управляете одноразовыми объектами. ВСЕГДА. Ваш код не сломается, если вы этого не сделаете, но он побеждает цель иметь одноразовые объекты, если вы этого не сделаете.
Общее правило для оптимизации GC:
- Любой класс, который управляет объектами, не управляемыми GC, должен реализовывать финализатор (и в целом должен также реализовать IDisposable). Здесь обычно выбираются одноразовые классы "верхнего уровня" - они обычно управляют РУЧКОЙ к окну, сокету, мьютексу или тому, что у вас есть.
- Любой класс, создающий объект IDisposable, должен реализовать IDisposable сам и правильно Dispose() его составляющих.
- Любая функция, которая создает объект IDisposeable, должна правильно Dispose(), когда она была использована. Не позволяйте ему просто выходить из сферы действия.
Эти правила могут быть согнуты или проигнорированы, если вы пишете приложение для себя, но при распространении кода другим вы должны быть профессиональными и следовать правилам.
Логика здесь заключается в том, что когда вы управляете памятью вне представления GC, GC-движок не может правильно управлять использованием вашей памяти. Например, на вашей кучке .NET вы можете просто указать 4-байтовый указатель, но на неуправляемой земле вы можете указать 200 МБ памяти. Механизм GC не будет пытаться собрать их, пока у вас не будет нескольких десятков, потому что все, что он видит, - это несколько байтов; в то время как в реальном мире он очень похож на утечку памяти.
Поэтому правило: неуправляемая память должна немедленно освободиться, когда вы закончите с ней (цепочка IDisposable делает это для вас), тогда как управляемая память освобождается движком GC всякий раз, когда она приближается к ней.
Ответ 2
Да, ваш класс должен быть IDisposable, если ему нужно распоряжаться любыми объектами, которые он использует. Примером этого является StreamReader. Он реализует IDisposable, поэтому он может распоряжаться связанным с ним объектом потока.
Ответ 3
Если я правильно понял ваш вопрос, у вас есть класс, который использует DbConnection. Вы хотите убедиться, что DbConnection правильно настроен, когда вы закончите работу с ним или когда ваш класс удален. Существует несколько способов достичь этого.
Если вы используете соединение с базой данных как локальную переменную в методе, вы можете использовать инструкцию using() {}.
using (SqlConnection sqlConnection = new SqlConnection(connStr))
{
...do stuff with connection here
}
Оператор using() {} автоматически вызывает Dispose() для объектов, объявленных в(). (Он также требует, чтобы объекты, объявленные в() реализуют IDisposable, чтобы обеспечить их размещение)
Если вместо этого вы работаете с DbConnection как частной переменной, которая инициализируется во время построения объекта или каким-либо другим методом инициализации, вы, вероятно, захотите реализовать IDisposable самостоятельно, а затем вызовите метод _dbConnection.Dispose() в свой метод Dispose(). Таким образом, когда ваш объект будет удален, объект соединения db также будет удален.
public class MyDALObj : IDisposable
{
public MyDalObj()
{
... create _dbConn object ...
}
public void Dispose()
{
_dbConn.Dispose();
}
private DbConnection _dbConn;
}
Ответ 4
Вы должны сделать это, так как это единственный способ для пользователя вашего класса убедиться, что встроенный ресурс правильно расположен.
Однако шаблон, используемый для Dispose(), может немного отличаться от того, что обычно записывается, в этом случае, поскольку вам не нужно различать неуправляемые и управляемые ресурсы (ваш инкапсулированный ресурс всегда рассматривается как "управляемый" "ресурс" ).
Я написал подробное сообщение в блоге по этой теме - Инкапсулирование IDisposable Resources.
Ответ 5
Существует два разных сценария:
- Вашему объекту предоставляется ссылка на объект для использования либо с помощью аргумента конструктора, либо с помощью свойства, и этот объект реализует IDisposable.
- Ваш объект создает экземпляр объекта, который реализует IDisposable.
Во втором случае ваш объект несет ответственность за задействованные ресурсы, поэтому ваш объект должен реализовать IDisposable, а при удалении вы должны избавиться от объекта, который вы создали.
DbConnection подпадает под этот второй случай, поэтому да, ваш объект должен реализовать IDisposable, а затем удалять соединение.
В первом случае вам нужно решить следующие три решения:
- Ваш объект ссылается только на внешний объект. Ваш объект не должен удалять этот внешний объект. Вам не нужно реализовывать IDisposable для этого случая (то есть для этого конкретного объекта, если вы также внутренне создаете одноразовый объект, вы вернетесь ко второму случаю выше).
- Ваш объект берет на себя ответственность за внешний объект. В этом случае вы вернетесь ко второму случаю, даже если ваш объект не является тем, кто создает этот внешний объект. Здесь вы реализуете IDisposable и удаляете объект, который вам предоставлен.
- Вы реализуете способ для внешнего мира рассказать вам, какое из первых двух решений выбрать. Например, конструктору может быть предоставлено соединение и логический аргумент (или, в идеале, значение перечисления), который сообщает конструктору, принадлежит ли вашему объекту поставляемое соединение. Здесь вам также необходимо реализовать IDisposable, но в методе Dispose вам нужно проверить право собственности и удалить только поставляемое соединение, если вы владеете им.
Это было много текста, поэтому позвольте мне подвести итог:
- Объекты, которыми вы владеете, вам нужно избавиться.
- Объекты, которые у вас нет, вы не распоряжаетесь.
Также есть третий случай, который звучит не так, как будто у вас есть, но тем не менее.
В случае, когда вы создаете, используете и удаляете объект локально внутри одного метода, не передавая его или сохраняя в полях класса, вместо этого вы используете оператор using
, например:
using (IDbConnection conn = ....())
{
}
Ответ 6
Это, безусловно, лучший способ сделать это, особенно при работе с тяжелыми/неуправляемыми объектами.
Изменить: Лучшая практика, но не обязательная.
Ответ 7
Поскольку мы никогда не знаем, когда объект собирается собираться GC, мы используем интерфейс IDisposable, чтобы иметь возможность намеренно выпускать неуправляемые ресурсы до того, как объект будет собран мусором.
Если одноразовый объект не будет удален до его сбора, его ресурсы могут не быть выпущены до выхода AppDomain.
Это почти неписаное правило, согласно которому каждый объект, имеющий ссылку на объект IDisposable, должен быть сам IDisposable и вызывать метод Dispose своих ссылок на IDisposable в своем собственном методе Dispose.
Ответ 8
Конечно, вы можете удалить значительную (ID) стоимость реализации IDisposable и получить что-то довольно близкое к детерминированной финализации объектов в управляемой куче, если вы используете С++/CLI. Это часто (я нахожу) упущенный аспект языка, который многие люди, кажется, отправляют в "для кода только для клея".
Ответ 9
Когда вы предоставляете явный контроль с помощью Dispose, вы должны предоставить неявную очистку, используя метод Finalize. Finalize предоставляет резервную копию для предотвращения утечки ресурсов, если программисту не удается вызвать Dispose.
Я думаю, что лучший способ реализовать это - использовать комбинацию метода Dispose и Finalize.
Вы можете найти более здесь.