lunes, 30 de marzo de 2015

Snippet para IDisposable

Si hay algún snippet que echo especialmente en falta en Visual Studio es uno para implementar el patrón IDisposable.

Quizás me haya perdido algo, pero no lo encuentro y tampoco R# me ayuda con ello.

En cualquier caso, he recordado que escribí hace tiempo un post sobre la extensión Snippet Designer y cómo te ayudaba a la hora de escribir un nuevo snippet.

Basándome en la implementación del patrón IDisposable que he encontrado en IDisposable, Done Right y utilizando la función ClassName que está disponible dentro del código de un snippet, he creado un snippet que automatiza la tarea.

Actualizado. Como bien me han corregido en los comentarios, es importante saber si se está o no tratando con recursos no-manejados. En caso afirmativo el patrón sería el que se muestra, pero en caso contrario habría que no redefinir el finalizador ni llamar a GC.SupressFinalizer(this);

Para que Visual Studio reconozca el snippet simplemente tienes que crear un fichero con la extensión .snippet y el código que te muestro a continuación, en C:\Users\<TuUsuario>\Documents\Visual Studio 2013\Code Snippets\Visual C#\My Code Snippets.

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
<Title>Disposable</Title>
<Author>panicoenlaxbox@gmail.com</Author>
<Description>IDisposable pattern</Description>
<HelpUrl>https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/</HelpUrl>
<Shortcut>disp</Shortcut>
</Header>
<Snippet>
<Declarations>
<Object Editable="false">
<ID>classname</ID>
<ToolTip>
</ToolTip>
<Default>
</Default>
<Function>ClassName()</Function>
<Type>
</Type>
</Object>
</Declarations>
<Code Language="csharp" Delimiter="$"><![CDATA[ bool _disposed;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

~$classname$()
{
Dispose(false);
}

protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;

if (disposing)
{
// free other managed objects that implement
// IDisposable only
}

// release any unmanaged objects
// set the object references to null

_disposed = true;
}]]></Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>

Un saludo!

6 comentarios:

  1. Buenas!
    Un par de comentarios ;-)

    1. GC.SupressFinalize solo debería llamarse en el Dispose si se ha redefinido el método Finalize.
    2. El método Finalize solo debería redefinirse para llamar a Dispose(false) si el método Dispose(bool) contiene código para tratar con código NO manejado.

    Saludos! ;-)

    ResponderEliminar
    Respuestas
    1. El "GC.SuppressFinalize(this)" sí está bien utilizado.

      public void Dispose()
      {
      Dispose(true);

      // Sólo se suprime el "Finalize()" si el "Dispose(...)"
      // se ha ejecutado correctamente
      GC.SuppressFinalize(this);
      }

      Capítulo 9.4.1 de "Framework Design Guidelines".

      Eliminar
    2. Buenas!

      Si NO defines Finalize no necesites GC.SupressFinalize, ya que no hace nada. Puedes usarlo sí, pero no lo necesitas.

      "if obj does not have a finalizer, the call to the SuppressFinalize method has no effect." - https://msdn.microsoft.com/en-us/library/system.gc.suppressfinalize.aspx

      Saludos!

      Eliminar
  2. Es una mala práctica incluir el "finalizer" siempre que se implementa "IDisposable". El "finalizer" está pensado para eliminar recursos no manejados cuando el GC procede al borrado definitivo del objeto. Incluir "finalizer" penaliza mucho el rendimiento y aumenta la complejidad, se recomienda no utilizarlo.

    Problemas de los objetos finalizables:

    1. Como se tienen que guardar en una lista de objetos finalizables la creación de instancias es más lenta.

    2. Cuando se detecta que un objeto finalizable es "unreachable" se mueve a una cola "to-be-finalized".

    3. Sólo hay un hilo para ejecutar los "finalizers", por lo tanto si hay muchos "finalizers" puede haber problemas de escalabilidad.

    Si es necesario liberar recursos no manejados mejor utilizar un tipo que encapsule estas operaciones (wrapper). Este tipo sí tiene que implementar el patrón finalizable.

    .NET ofrece un "wrapper" para este tipo de funcionalidad: SafeHandle. Un "wrapper" que herede de esta clase suele ser la mejor opción.

    Todo esto viene explicado en el capítulo 9.4.2 del libro "Framework Design Guidelines".

    ResponderEliminar
    Respuestas
    1. Estoy pensando... :)

      Entonces en su versión más básica (sólo tratando con objetos manejados) quedaría muy sencillo, algo así:

      bool _disposed;

      public void Dispose()
      {
      if (_disposed)
      return;

      // free other managed objects that implement
      // IDisposable only

      _disposed = true;
      }

      Eliminar
  3. Gracias por los comentarios!
    Esto es lo bueno de publicar, que he aprendido más con lo que me corregís que con lo que digo! :)
    Queda claro que si no se tiene código para tratar con recursos no-manejados (la parte del comentario "release any unmanaged objects, set the object references to null"), no hara falta el finalizador (de hecho es mala práctica con consecuencias de rendimiento como bien ha explicado @Balder), luego no haría falta tampoco el GC.SupressFinalize, luego quedaría bastante más compacto el código y más correcto.
    Gracias!

    ResponderEliminar