Desechable o no desechable

by Valeriano Tortola 12. julio 2008 00:41

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 :D

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.

Tags: , , ,

.NET 2.0 | C# 2.0

Comentarios

12/07/2008 1:45:41 #

trackback

Trackback from Pensando en asíncrono

Desechable o no desechable

Pensando en asíncrono |

12/07/2008 1:46:14 #

trackback

Trackback from vtortola

Desechable o no desechable

vtortola |

Comentarios no permitidos