Desechable o no desechable

La interfaz IDisposable nos provee del método .Dispose() que utilizamos para liberar los recursos que esta usando ese objeto, pero dicho método… no deja de ser un simple método 😉 Solo hace falta hacer una pequeña prueba para darse cuenta:

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:          Desechable test = new Desechable();
   6:            
   7:          test.Dispose();
   8:   
   9:          Console.WriteLine(test.Cadena??"Muerto"); // Muestra: Vivo!
  10:          Console.ReadKey(true);
  11:      }
  12:  }
  13:   
  14:  class Desechable : IDisposable
  15:  {
  16:      public String Cadena = "Vivo!";
  17:   
  18:      public void Dispose()
  19:      {
  20:   
  21:      }
  22:  }

Ok ok… un poco más complejo:

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:          Desechable test = new Desechable();
   6:          WeakReference wr = new WeakReference(test.Tabla.Columns[0]);
   7:   
   8:          test.Dispose();
   9:          test = null;
  10:   
  11:          Console.WriteLine(((DataColumn)wr.Target).ColumnName); // Muestra "Columna"
  12:          Console.ReadKey(true);
  13:      }
  14:  }
  15:   
  16:  class Desechable : IDisposable
  17:  {
  18:      public DataTable Tabla = new DataTable();
  19:   
  20:      public Desechable()
  21:      {
  22:          Tabla.Columns.Add(new DataColumn("Columna"));
  23:      }
  24:   
  25:      public void Dispose()
  26:      {
  27:          Tabla.Dispose();
  28:      }
  29:  }

El resultado cambia si en la línea 10 hacemos un GC.Collect() ;D

Como vemos, el único que puede liberar memoria en el .NET Framework es el Garbage Collector, por lo que el método .Dispose() no libera memoria in libera nada, solo nos sirve para asegurar que el objeto que estamos desechando ha cerrado correctamente todos sus recursos y podemos olvidarnos de él, ya se encargará el GC de liberar la memoria cuando lo crea necesario.

Entonces, implementar la interfaz IDisposable no hace nuestros objetos “destruibles” bajo demanda, ni setear todos los campos a null en Dispose no va a hacer que sea recolectado más deprisa, ni no hacerlo va a evitar que sea recolectado, ni debemos des-subscribir los eventos…

Por lo tanto, debemos implementar el patrón IDisposable en una clase siempre que:

  • Nuestra clase deriva de una clase que lo implementa.
  • Nuestra clase esta compuesta de otras clases que lo implementan.
  • Hagamos uso de recursos no administrados.

Para todo lo demás… mastercard confia en el GC 😀

Un tema que causa controversia en este aspecto es… ¿que pasa con los delegados? ¿Como el estar subscrito a delegados afecta a la recolección de memoria? Bien, el estar subscrito a un delegado de una clase, no afecta en su recolección:

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:          MiClase test = new MiClase();
   6:          WeakReference wr = new WeakReference(test);
   7:          
   8:          test.MiEvento += new EventHandler(test_MiEvento);
   9:   
  10:          test = null;
  11:          GC.Collect();
  12:   
  13:          Console.WriteLine(wr.IsAlive ? "Vivo" : "Muerto"); // Muestra "Muerto"
  14:          Console.ReadKey(true);
  15:      }
  16:   
  17:      static void test_MiEvento(object sender, EventArgs e)
  18:      {
  19:          throw new NotImplementedException();
  20:      }
  21:  }
  22:   
  23:  class MiClase
  24:  {
  25:      public event EventHandler MiEvento;
  26:  }

Pero al revés, es decir, que una clase este subscrita a uno de nuestros delegados… si provoca que el objeto no pueda ser recolectado:

   1:  class Program
   2:  {
   3:      static event EventHandler Evento;
   4:      static void Main(string[] args)
   5:      {
   6:          MiClase test = new MiClase();
   7:          WeakReference wr = new WeakReference(test);
   8:   
   9:          Evento += test.Manejador_Evento;
  10:   
  11:          test = null;
  12:          GC.Collect();
  13:   
  14:          Console.WriteLine(wr.IsAlive ? "Vivo" : "Muerto"); // Muestra "Vivo"
  15:          Console.WriteLine(Evento.GetInvocationList().Length); // Muestra 1
  16:          Console.ReadKey(true);
  17:      }
  18:  }
  19:   
  20:  class MiClase
  21:  {
  22:      public void Manejador_Evento(object sender, EventArgs e)
  23:      {
  24:          throw new NotImplementedException();
  25:      }
  26:  }

Y ojo, que los delegados son muy comodos para ejecutar muchos métodos de una pasada (por ejemplo podríamos tener una serie de objetos subscritos a un delegado y cada vez que invocaramos a este se ejecutaria ese método en todos los objetos), pero como veis pueden causar un memory leak; aunque en ese caso poco podemos hacer desde el método Dispose ya que es otra clase la que ha de desuscribirse.

Saludos desde el frio Dublin donde el Verano es una broma de mal gusto.

Desechable o no desechable| vtortola.NET

2 comentarios en “Desechable o no desechable”

  1. Interesante… y qué diferencia hay entre implementar un destructor o el método Dispose en una clase? También existe la posibilidad de implementar ambas cosas a la vez. Esto último es realmente útil o necesario??

  2. Dale un vistazo a esto : http://www.vtortola.net/post/Objetos-desechables-con-la-interfaz-IDisposable.aspx

    Cuando un objeto va a ser recolectado, si tiene el método destructor, el GC lo manda a la cola de destrucción donde es ejecutado dicho método, si no, lo destruye sin más. Dispose es un método que poco ó nada tiene que ver con el GC, es simplemente para asegurarnos de que queda “listo para sentencia”.

    En el link que te pongo, muestro que es útil implementar el patrón desechable de esa forma que aparece, así si olvidamos llamar a .Dispose, el GC al ejecutar el destructor, este llamará al dispose por nosotros 🙂

    El problema con el destructor, es que no sabemos cuando va a ser llamado, incluso podría no llamarse nunca si por error mantenemos una referencia hacia el objeto o si hacemos un GC.SupressFinalizer.

    Un saludo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *