Uso del método Finalizar/Disponer en C#
c#2008
He estado trabajando en esto por un tiempo y todavía estoy confundido acerca del uso de los métodos de finalización y eliminación en el código. Mis preguntas están a continuación:
Sé que solo necesitamos un finalizador mientras desechamos los recursos no administrados. Sin embargo, si hay recursos administrados que realizan llamadas a recursos no administrados, ¿aún sería necesario implementar un finalizador?
Sin embargo, si desarrollo una clase que no utiliza ningún recurso no administrado, directa o indirectamente, ¿debería implementar
IDisposable
para permitir que los clientes de esa clase utilicen la 'declaración de uso'?¿Sería factible implementar IDisposable solo para permitir que los clientes de su clase usen la declaración de uso?
using(myClass objClass = new myClass()) { // Do stuff here }
He desarrollado este código simple a continuación para demostrar el uso de Finalizar/eliminar:
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); } }
Pregunta sobre el código fuente:
Aquí no he agregado el finalizador, y normalmente el GC llamará al finalizador, y el finalizador llamará a Dispose. Como no tengo el finalizador, ¿cuándo llamo al método Dispose? ¿Es el cliente de la clase el que tiene que llamarlo?
Entonces, mi clase en el ejemplo se llama NoGateway y el cliente podría usar y eliminar la clase de esta manera:
using(NoGateway objNoGateway = new NoGateway()) { // Do stuff here }
¿Se llamará automáticamente al método Dispose cuando la ejecución llegue al final del bloque de uso, o el cliente tendrá que llamar manualmente al método dispose? es decir
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
Estoy usando la
WebClient
clase en miNoGateway
clase. Debido aWebClient
que implementa laIDisposable
interfaz, ¿significa esto queWebClient
utiliza indirectamente recursos no administrados? ¿Existe una regla estricta y rápida para seguir esto? ¿Cómo sé que una clase utiliza recursos no administrados?
El patrón IDisposable recomendado está aquí . Al programar una clase que usa IDisposable, generalmente debes usar dos patrones:
Al implementar una clase sellada que no utiliza recursos no administrados, simplemente implementa un método Dispose como con las implementaciones de interfaz normales:
public sealed class A : IDisposable
{
public void Dispose()
{
// get rid of managed resources, call Dispose on member variables...
}
}
Al implementar una clase abierta, hágalo así:
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);
//}
}
Observe que no he declarado un finalizador en B
; solo debe implementar un finalizador si tiene recursos reales no administrados para eliminar. El CLR trata los objetos finalizables de manera diferente a los objetos no finalizables, incluso si SuppressFinalize
se llama.
Por lo tanto, no debes declarar un finalizador a menos que sea necesario, pero les brindas a los herederos de tu clase un gancho para llamar Dispose
e implementar un finalizador ellos mismos si usan recursos no administrados directamente:
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);
}
}
Si no está utilizando recursos no administrados directamente ( SafeHandle
y los amigos no cuentan, ya que declaran sus propios finalizadores), no implemente un finalizador, ya que el GC trata las clases finalizables de manera diferente, incluso si luego suprime el finalizador. También tenga en cuenta que, aunque B
no tiene un finalizador, todavía llama SuppressFinalize
para tratar correctamente cualquier subclase que implemente un finalizador.
Cuando una clase implementa la interfaz IDisposable, significa que en algún lugar hay algunos recursos no administrados de los que debes deshacerte cuando hayas terminado de usar la clase. Los recursos reales están encapsulados dentro de las clases; no es necesario eliminarlos explícitamente. Simplemente llamar Dispose()
o envolver la clase en un using(...) {}
asegurará que los recursos no administrados se eliminen según sea necesario.
El patrón oficial a implementar IDisposable
es difícil de entender. Creo que este es mejor :
public class BetterDisposableClass : IDisposable {
public void Dispose() {
CleanUpManagedResources();
CleanUpNativeResources();
GC.SuppressFinalize(this);
}
protected virtual void CleanUpManagedResources() {
// ...
}
protected virtual void CleanUpNativeResources() {
// ...
}
~BetterDisposableClass() {
CleanUpNativeResources();
}
}
Una solución aún mejor es tener una regla que indique que siempre debe crear una clase contenedora para cualquier recurso no administrado que necesite manejar:
public class NativeDisposable : IDisposable {
public void Dispose() {
CleanUpNativeResource();
GC.SuppressFinalize(this);
}
protected virtual void CleanUpNativeResource() {
// ...
}
~NativeDisposable() {
CleanUpNativeResource();
}
}
Con SafeHandle
y sus derivados, estas clases deberían ser muy raras .
El resultado para las clases desechables que no tratan directamente con recursos no administrados, incluso en presencia de herencia, es poderoso: ya no necesitan preocuparse por los recursos no administrados . Serán simples de implementar y comprender:
public class ManagedDisposable : IDisposable {
public virtual void Dispose() {
// dispose of managed resources
}
}
Tenga en cuenta que cualquier implementación de IDisposable debe seguir el siguiente patrón (en mi humilde opinión). Desarrollé este patrón basándome en información de varios "dioses" excelentes de .NET, las pautas de diseño de .NET Framework (tenga en cuenta que MSDN no sigue esto por alguna razón). Las pautas de diseño de .NET Framework fueron escritas por Krzysztof Cwalina (arquitecto de CLR en ese momento) y Brad Abrams (creo que el gerente del programa CLR en ese momento) y Bill Wagner ([C# efectivo] y [C# más efectivo] (solo tome un busque estos en Amazon.com:
Tenga en cuenta que NUNCA debe implementar un Finalizador a menos que su clase contenga directamente (no herede) recursos NO administrados. Una vez que implementas un Finalizer en una clase, incluso si nunca se llama, se garantiza que vivirá para una colección adicional. Se coloca automáticamente en la cola de finalización (que se ejecuta en un solo subproceso). Además, una nota muy importante... ¡todo el código ejecutado dentro de un Finalizador (en caso de que necesite implementar uno) DEBE ser seguro para subprocesos Y seguro para excepciones! De lo contrario, sucederán cosas MALAS... (es decir, un comportamiento indeterminado y, en el caso de una excepción, un fallo fatal e irrecuperable de la aplicación).
El patrón que he creado (y para el que he escrito un fragmento de código) es el siguiente:
#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
Aquí está el código para implementar IDisposable en una clase derivada. Tenga en cuenta que no es necesario incluir explícitamente la herencia de IDisposable en la definición de la clase derivada.
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 );
}
}
Publiqué esta implementación en mi blog en: Cómo implementar correctamente el patrón de eliminación
Estoy de acuerdo con pm100 (y debería haberlo dicho explícitamente en mi publicación anterior).
Nunca debes implementar IDisposable en una clase a menos que lo necesites. Para ser muy específico, hay alrededor de 5 ocasiones en las que alguna vez necesitarías/deberías implementar IDisposable:
Su clase contiene explícitamente (es decir, no a través de herencia) cualquier recurso administrado que implemente IDisposable y debe limpiarse una vez que su clase ya no se use. Por ejemplo, si su clase contiene una instancia de Stream, DbCommand, DataTable, etc.
Su clase contiene explícitamente cualquier recurso administrado que implemente un método Close(), por ejemplo, IDataReader, IDbConnection, etc. Tenga en cuenta que algunas de estas clases implementan IDisposable al tener Dispose() así como un método Close().
Su clase contiene explícitamente un recurso no administrado, por ejemplo, un objeto COM, punteros (sí, puede usar punteros en C# administrado pero deben declararse en bloques "no seguros", etc. En el caso de recursos no administrados, también debe asegurarse de llame a System.Runtime.InteropServices.Marshal.ReleaseComObject() en el RCW. Aunque el RCW es, en teoría, un contenedor administrado, todavía hay un recuento de referencias bajo las sábanas.
Si su clase se suscribe a eventos utilizando referencias sólidas. Necesitas darte de baja/desvincularte de los eventos. ¡Siempre asegúrese de que estos no sean nulos antes de intentar cancelar el registro o separarlos!
Su clase contiene cualquier combinación de lo anterior...
Una alternativa recomendada para trabajar con objetos COM y tener que usar Marshal.ReleaseComObject() es usar la clase System.Runtime.InteropServices.SafeHandle.
El BCL (equipo de biblioteca de clase base) tiene una buena publicación de blog al respecto aquí http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
Una nota muy importante a tener en cuenta es que si está trabajando con WCF y limpiando recursos, CASI SIEMPRE debe evitar el bloque "usar". Hay muchas publicaciones en blogs y algunas en MSDN sobre por qué esto es una mala idea. También publiqué sobre esto aquí: no use 'using()' con un proxy WCF