Завершение / Утилизация шаблона в C#
C# 2008
Я работал над этим некоторое время, и я все еще запутался в некоторых вопросах. Мои вопросы ниже
Я знаю, что вам нужен финализатор, только если вы избавляетесь от неуправляемых ресурсов. Однако, если вы используете управляемые ресурсы, которые делают вызовы неуправляемым ресурсам, вам все равно нужно будет реализовать финализатор?
Однако, если вы разрабатываете класс, который не использует какие-либо неуправляемые ресурсы, прямо или косвенно, вы можете реализовать
IDisposable
чтобы клиенты вашего класса могли использовать "оператор использования"?Было бы приемлемо реализовать IDisposable только для того, чтобы клиенты вашего класса могли использовать оператор using?
using(myClass objClass = new myClass()) { // Do stuff here }
Я разработал этот простой код ниже, чтобы продемонстрировать шаблон Finalize / dispose:
public class NoGateway : IDisposable { private WebClient wc = null; public NoGateway() { wc = new WebClient(); wc.DownloadStringCompleted += wc_DownloadStringCompleted; } // Start the Async call to find if NoGateway is true or false public void NoGatewayStatus() { // Start the Async's download // Do other work here wc.DownloadStringAsync(new Uri(www.xxxx.xxx)); } private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // Do work here } // Dispose of the NoGateway object public void Dispose() { wc.DownloadStringCompleted -= wc_DownloadStringCompleted; wc.Dispose(); GC.SuppressFinalize(this); } }
Вопрос по исходному коду:
Здесь я не добавил финализатор, и обычно финализатор вызывается GC, а финализатор вызывает Dispose. Поскольку у меня нет финализатора, когда я вызываю метод Dispose? Это клиент класса должен вызывать его?
Таким образом, мой класс в примере называется NoGateway, и клиент может использовать и распоряжаться этим классом следующим образом:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
Будет ли метод Dispose автоматически вызываться, когда выполнение достигает конца блока using, или клиент должен вручную вызывать метод dispose? т.е.
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
Я использую класс веб-клиента в моем
NoGateway
учебный класс. Поскольку веб-клиент реализует интерфейс IDisposable, означает ли это, что веб-клиент косвенно использует неуправляемые ресурсы? Есть ли жесткое и быстрое правило, которому нужно следовать по этому поводу? Как мне узнать, что класс использует неуправляемые ресурсы?
13 ответов
Рекомендуемый шаблон IDisposable находится здесь. При программировании класса, который использует IDisposable, обычно вы должны использовать два шаблона:
При реализации запечатанного класса, который не использует неуправляемые ресурсы, вы просто реализуете метод Dispose, как в обычных реализациях интерфейса:
public sealed class A : IDisposable
{
public void Dispose()
{
// get rid of managed resources, call Dispose on member variables...
}
}
При реализации открытого класса, сделайте это так:
public class B : IDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// get rid of managed resources
}
// get rid of unmanaged resources
}
// only if you use unmanaged resources directly in B
//~B()
//{
// Dispose(false);
//}
}
Обратите внимание, что я не объявил финализатор в B
; вы должны реализовывать финализатор только в том случае, если у вас есть реальные неуправляемые ресурсы, которыми можно распоряжаться. CLR работает с финализуемыми объектами иначе, чем с не финализируемыми объектами, даже если SuppressFinalize
называется.
Таким образом, вы не должны объявлять финализатор без необходимости, но вы даете наследникам вашего класса ловушку для вызова вашего Dispose
и реализовать финализатор самостоятельно, если они напрямую используют неуправляемые ресурсы:
public class C : B
{
private IntPtr m_Handle;
protected override void Dispose(bool disposing)
{
if (disposing)
{
// get rid of managed resources
}
ReleaseHandle(m_Handle);
base.Dispose(disposing);
}
~C() {
Dispose(false);
}
}
Если вы не используете неуправляемые ресурсы напрямую (SafeHandle
и друзья не учитываются, так как они объявляют свои собственные финализаторы), а затем не реализуют финализатор, поскольку сборщик мусора обрабатывает финализируемые классы по-разному, даже если позже вы подавите финализатор. Также обратите внимание, что, хотя B
не имеет финализатора, он все еще вызывает SuppressFinalize
правильно иметь дело с любыми подклассами, которые реализуют финализатор.
Когда класс реализует интерфейс IDisposable, это означает, что где-то есть неуправляемые ресурсы, от которых нужно избавиться, когда вы закончите использовать класс. Фактические ресурсы инкапсулированы в классах; вам не нужно явно удалять их. Просто звоню Dispose()
или завернуть класс в using(...) {}
убедитесь, что любые неуправляемые ресурсы будут удалены по мере необходимости.
Официальный шаблон для реализации IDisposable
трудно понять. Я считаю, что этот лучше
public class BetterDisposableClass : IDisposable {
public void Dispose() {
CleanUpManagedResources();
CleanUpNativeResources();
GC.SuppressFinalize(this);
}
protected virtual void CleanUpManagedResources() {
// ...
}
protected virtual void CleanUpNativeResources() {
// ...
}
~BetterDisposableClass() {
CleanUpNativeResources();
}
}
Еще лучшим решением является правило, что вам всегда нужно создавать класс-оболочку для любого неуправляемого ресурса, с которым вам нужно работать:
public class NativeDisposable : IDisposable {
public void Dispose() {
CleanUpNativeResource();
GC.SuppressFinalize(this);
}
protected virtual void CleanUpNativeResource() {
// ...
}
~NativeDisposable() {
CleanUpNativeResource();
}
}
С SafeHandle
и его производные, эти классы должны быть очень редкими.
Результат для одноразовых классов, которые не имеют непосредственного отношения к неуправляемым ресурсам, даже при наличии наследования, является мощным: им больше не нужно заботиться о неуправляемых ресурсах. Их будет легко реализовать и понять:
public class ManagedDisposable : IDisposable {
public virtual void Dispose() {
// dispose of managed resources
}
}
Обратите внимание, что любая реализация IDisposable должна следовать шаблону ниже (IMHO). Я разработал этот шаблон на основе информации от нескольких превосходных "богов" .NET - Руководства по проектированию.NET Framework (обратите внимание, что MSDN по какой-то причине не следует этому!). Руководство по проектированию.NET Framework было написано Кшиштофом Квалиной (архитектором CLR в то время) и Брэдом Абрамсом (я считаю, в то время руководителем программы CLR) и Биллом Вагнером ([Эффективный C#] и [Более эффективный C#] (просто возьмите ищите их на Amazon.com:
Обратите внимание, что вы НИКОГДА не должны реализовывать Finalizer, если ваш класс не содержит (не наследует) неуправляемых ресурсов. Как только вы реализуете Finalizer в классе, даже если он никогда не вызывается, он гарантированно будет жить для дополнительной коллекции. Он автоматически помещается в очередь финализации (которая выполняется в одном потоке). Кроме того, одно очень важное замечание... весь код, выполняемый в Финализаторе (если вам необходимо его реализовать), ДОЛЖЕН быть поточно-ориентированным и исключительным! ПЛОХИЕ вещи будут происходить иначе... (то есть неопределенное поведение и в случае исключения фатальный неисправимый сбой приложения).
Шаблон, который я собрал (и написал фрагмент кода), выглядит следующим образом:
#region IDisposable implementation
//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable
// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }
/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
public void Dispose()
{
Dispose( true );
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
// Always use SuppressFinalize() in case a subclass
// of this type implements a finalizer.
GC.SuppressFinalize( this );
}
/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> equals false, the method has been called by the
/// runtime from inside the finalizer and you should not reference
/// other objects. Only unmanaged resources can be disposed.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
// TODO If you need thread safety, use a lock around these
// operations, as well as in your methods that use the resource.
try
{
if( !this.IsDisposed )
{
if( isDisposing )
{
// TODO Release all managed resources here
$end$
}
// TODO Release all unmanaged resources here
// TODO explicitly set root references to null to expressly tell the GarbageCollector
// that the resources have been disposed of and its ok to release the memory allocated for them.
}
}
finally
{
// explicitly call the base class Dispose implementation
base.Dispose( isDisposing );
this.IsDisposed = true;
}
}
//TODO Uncomment this code if this class will contain members which are UNmanaged
//
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
// ~$className$()
// {
// Dispose( false );
// }
#endregion IDisposable implementation
Вот код для реализации IDisposable в производном классе. Обратите внимание, что вам не нужно явно указывать наследование от IDisposable в определении производного класса.
public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)
protected override void Dispose( bool isDisposing )
{
try
{
if ( !this.IsDisposed )
{
if ( isDisposing )
{
// Release all managed resources here
}
}
}
finally
{
// explicitly call the base class Dispose implementation
base.Dispose( isDisposing );
}
}
Я разместил эту реализацию в своем блоге по адресу: Как правильно реализовать шаблон утилизации
Я согласен с pm100 (и должен был четко сказать это в моем предыдущем посте).
Вы никогда не должны реализовывать IDisposable в классе, если вам это не нужно. Чтобы быть очень конкретным, есть примерно 5 раз, когда вам понадобится / нужно реализовать IDisposable:
Ваш класс явно содержит (т.е. не через наследование) любые управляемые ресурсы, которые реализуют IDisposable и должны быть очищены, когда ваш класс больше не используется. Например, если ваш класс содержит экземпляр Stream, DbCommand, DataTable и т. Д.
Ваш класс явно содержит любые управляемые ресурсы, которые реализуют метод Close() - например, IDataReader, IDbConnection и т. Д. Обратите внимание, что некоторые из этих классов реализуют IDisposable с помощью метода Dispose(), а также метода Close().
Ваш класс явно содержит неуправляемый ресурс - например, COM-объект, указатели (да, вы можете использовать указатели в управляемом C#, но они должны быть объявлены в "небезопасных" блоках и т. Д. В случае неуправляемых ресурсов вы также должны убедиться, что вызовите System.Runtime.InteropServices.Marshal.ReleaseComObject() в RCW. Несмотря на то, что RCW, теоретически, является управляемой оболочкой, подсчет ссылок по-прежнему продолжается.
Если ваш класс подписывается на события, используя сильные ссылки. Вы должны отменить регистрацию / отсоединиться от событий. Всегда проверяйте, чтобы они не были нулевыми, прежде чем пытаться отменить их регистрацию!
Ваш класс содержит любую комбинацию из вышеперечисленного...
Рекомендуемой альтернативой работе с COM-объектами и использованием Marshal.ReleaseComObject () является использование класса System.Runtime.InteropServices.SafeHandle.
У BCL (команды библиотеки базовых классов) есть хороший пост в блоге об этом здесь http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
Одно очень важное замечание: если вы работаете с WCF и очищаете ресурсы, ПОЧТИ ВСЕГДА следует избегать использования блока. Есть много постов в блоге и некоторые на MSDN о том, почему это плохая идея. Я также написал об этом здесь - не используйте 'using()' с прокси WCF
Использование лямбды вместо IDisposable.
Я никогда не был в восторге от всей идеи использования /IDisposable. Проблема в том, что он требует от вызывающего абонента:
- знать, что они должны использовать IDisposable
- не забудьте использовать "использование".
Мой новый предпочтительный метод состоит в том, чтобы использовать фабричный метод и лямбду вместо
Представьте, что я хочу сделать что-то с SqlConnection (что-то, что должно быть заключено в использование). Классически вы бы сделали
using (Var conn = Factory.MakeConnection())
{
conn.Query(....);
}
Новый способ
Factory.DoWithConnection((conn)=>
{
conn.Query(...);
}
В первом случае вызывающая сторона может просто не использовать синтаксис using. Во втором случае у пользователя нет выбора. Нет метода, который создает объект SqlConnection, вызывающая сторона должна вызывать DoWithConnection.
DoWithConnection выглядит так
void DoWithConnection(Action<SqlConnection> action)
{
using (var conn = MakeConnection())
{
action(conn);
}
}
MakeConnection
сейчас приватный
Никто не ответил на вопрос о том, следует ли вам реализовать IDisposable, даже если он вам не нужен.
Краткий ответ: нет
Длинный ответ:
Это позволит потребителю вашего класса использовать "использование". Вопрос, который я хотел бы задать, - зачем им это делать? Большинство разработчиков не будут использовать "использование", если они не знают, что они должны - и как они знают. Или
- Это очевидно из опыта (класс сокетов, например)
- его документировано
- они осторожны и могут видеть, что класс реализует IDisposable
Поэтому, реализуя IDisposable, вы говорите разработчикам (по крайней мере, некоторым), что этот класс завершает что-то, что должно быть выпущено. Они будут использовать "использование" - но в других случаях использование невозможно (область действия объекта не локальна); и им придется начать беспокоиться о времени жизни объектов в этих других случаях - я бы волновался наверняка. Но это не обязательно
Вы реализуете Idisposable, чтобы они могли использовать использование, но они не будут использовать использование, если вы не скажете им об этом.
Так что не делай этого
Удалите шаблон:
public abstract class DisposableObject : IDisposable
{
public bool Disposed { get; private set;}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~DisposableObject()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing)
{
DisposeManagedResources();
}
DisposeUnmanagedResources();
Disposed = true;
}
}
protected virtual void DisposeManagedResources() { }
protected virtual void DisposeUnmanagedResources() { }
}
Пример наследования:
public class A : DisposableObject
{
public Component components_a { get; set; }
private IntPtr handle_a;
protected override void DisposeManagedResources()
{
try
{
Console.WriteLine("A_DisposeManagedResources");
components_a.Dispose();
components_a = null;
}
finally
{
base.DisposeManagedResources();
}
}
protected override void DisposeUnmanagedResources()
{
try
{
Console.WriteLine("A_DisposeUnmanagedResources");
CloseHandle(handle_a);
handle_a = IntPtr.Zero;
}
finally
{
base.DisposeUnmanagedResources();
}
}
}
public class B : A
{
public Component components_b { get; set; }
private IntPtr handle_b;
protected override void DisposeManagedResources()
{
try
{
Console.WriteLine("B_DisposeManagedResources");
components_b.Dispose();
components_b = null;
}
finally
{
base.DisposeManagedResources();
}
}
protected override void DisposeUnmanagedResources()
{
try
{
Console.WriteLine("B_DisposeUnmanagedResources");
CloseHandle(handle_b);
handle_b = IntPtr.Zero;
}
finally
{
base.DisposeUnmanagedResources();
}
}
}
Если вы используете другие управляемые объекты, которые используют неуправляемые ресурсы, вы не несете ответственности за их завершение. Ваша обязанность - вызывать Dispose для этих объектов, когда Dispose вызывается для вашего объекта, и он на этом останавливается.
Если ваш класс не использует дефицитных ресурсов, я не понимаю, почему вы заставили бы ваш класс реализовать IDisposable. Вы должны делать это только если вы:
- Знайте, что скоро у вас будут ограниченные ресурсы в ваших объектах, просто не сейчас (и я имею в виду, что, как в "мы все еще разрабатываем, он будет здесь до того, как мы закончили"), а не в "Я думаю, что нам это понадобится ")
- Использование ограниченных ресурсов
Да, код, который использует ваш код, должен вызывать метод Dispose вашего объекта. И да, код, который использует ваш объект может использовать
using
как вы показали.(Опять 2?) Вероятно, что WebClient использует неуправляемые ресурсы или другие управляемые ресурсы, которые реализуют IDisposable. Точная причина, однако, не важна. Важно то, что он реализует IDisposable, и поэтому вам приходится действовать на основе этих знаний, избавляясь от объекта, когда вы закончите с ним, даже если выясняется, что WebClient вообще не использует никаких других ресурсов.
Некоторые аспекты другого ответа немного некорректны по двум причинам:
Первый,
using(NoGateway objNoGateway = new NoGateway())
на самом деле эквивалентно:
try
{
NoGateway = new NoGateway();
}
finally
{
if(NoGateway != null)
{
NoGateway.Dispose();
}
}
Это может показаться смешным, поскольку оператор 'new' никогда не должен возвращать 'null', если у вас нет исключения OutOfMemory. Но рассмотрим следующие случаи: 1. Вы вызываете FactoryClass, который возвращает ресурс IDisposable, или 2. Если у вас есть тип, который может наследовать или не наследовать от IDisposable, в зависимости от его реализации - помните, что я видел, что шаблон IDisposable реализован неправильно во многих время на многих клиентах, где разработчики просто добавляют метод Dispose(), не наследуя от IDisposable (плохо, плохо, плохо). Вы также можете иметь дело с возвращением ресурса IDisposable из свойства или метода (опять же, плохо, плохо, плохо - не отдавайте свои ресурсы IDisposable)
using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
if (NoGateway != null)
{
...
Если оператор "as" возвращает нуль (или свойство или метод, возвращающий ресурс), а ваш код в блоке "using" защищает от "ноль", ваш код не будет взорван при попытке вызвать Dispose для нулевого объекта из-за встроенная нулевая проверка.
Вторая причина, по которой ваш ответ не точный, заключается в следующем:
Финализатор вызывается при уничтожении вашего объекта GC
Во-первых, финализация (как и сама сборка мусора) не является детерминированной. CLR определяет, когда он вызовет финализатор. т.е. разработчик / код понятия не имеет. Если шаблон IDisposable реализован правильно (как я уже писал выше) и был вызван GC.SuppressFinalize(), не будет вызван финализатор. Это одна из основных причин для правильной реализации шаблона. Поскольку для каждого управляемого процесса существует только 1 поток Finalizer, независимо от количества логических процессоров, вы можете легко снизить производительность, создав резервную копию или даже повесив поток Finalizer, забыв вызвать GC.SuppressFinalize().
Я разместил правильную реализацию шаблона Dispose в моем блоге: Как правильно реализовать шаблон Dispose
using(NoGateway objNoGateway = new NoGateway())
эквивалентно
try
{
NoGateway = new NoGateway();
}
finally
{
NoGateway.Dispose();
}
Финализатор вызывается при уничтожении вашего объекта GC. Это может быть в совершенно другое время, чем когда вы покидаете свой метод. Dispose of IDisposable вызывается сразу после того, как вы покинете блок using. Следовательно, шаблон обычно используется для освобождения ресурсов сразу после того, как они вам больше не нужны.
1) WebClient является управляемым типом, поэтому вам не нужен финализатор. Финализатор необходим в том случае, если ваши пользователи не Dispose() вашего класса NoGateway и после этого необходимо очистить собственный тип (который не собирается GC). В этом случае, если пользователь не вызывает Dispose(), содержащийся в нем WebClient будет удален GC сразу после того, как это сделает NoGateway.
2) Косвенно да, но вам не нужно об этом беспокоиться. Ваш код верен в действии, и вы не можете помешать вашим пользователям очень легко забыть Dispose().
Шаблон от MSDN
public class BaseResource: IDisposable
{
private IntPtr handle;
private Component Components;
private bool disposed = false;
public BaseResource()
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
Components.Dispose();
}
CloseHandle(handle);
handle = IntPtr.Zero;
}
disposed = true;
}
~BaseResource()
{ Dispose(false);
}
public void DoSomething()
{
if(this.disposed)
{
throw new ObjectDisposedException();
}
}
}
public class MyResourceWrapper: BaseResource
{
private ManagedResource addedManaged;
private NativeResource addedNative;
private bool disposed = false;
public MyResourceWrapper()
{
}
protected override void Dispose(bool disposing)
{
if(!this.disposed)
{
try
{
if(disposing)
{
addedManaged.Dispose();
}
CloseHandle(addedNative);
this.disposed = true;
}
finally
{
base.Dispose(disposing);
}
}
}
}
Насколько я знаю, настоятельно рекомендуется НЕ использовать Финализатор / Деструктор:
public ~MyClass() {
//dont use this
}
Главным образом, это происходит из-за незнания, когда или ЕСЛИ это будет вызвано. Метод утилизации намного лучше, особенно если вы используете или утилизируете напрямую.
использование это хорошо. используй это:)