POO. Ejercicio clases y objetos.

IC588201

CLASES

CLASES

 

Ejemplo básico:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class ClaseBasica
    {
        public int i = 10;
    }
    class Program
    {
        static void Main(string[] args)
        {
            object Objeto;
            Objeto = new ClaseBasica();
            ClaseBasica a;
            a = (ClaseBasica)Objeto;
            Console.WriteLine(a.i);

            ClaseBasica b = new ClaseBasica();
            Console.WriteLine(b.i);

            Console.ReadLine();
        }
    }
}

Comparar cómo hemos creado el objeto “a” y cómo el objeto “b“, el resultado es el mismo pero el objeto “b” fue creado directamente a partir de la clase “ClaseBasica” mientras que el objeto “a” fue creado a partir del objeto “Objeto“.

Veamos el ejemplo anterior ampliado:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{

    public class ClaseBasica
    {
        public int i = 10; //Campo
        private int tantoporcien; //Campo
        public int TantoPorCien //Propiedad
        {
            get { return tantoporcien; }
            set {
                if ((value >= 0) && (value <= 100))
                {
                    tantoporcien = value;
                }  
            }
        }
        public void Resetea() //Método
        {
            tantoporcien = 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            object Objeto;
            Objeto = new ClaseBasica();
            ClaseBasica a;
            a = (ClaseBasica)Objeto;
            a.i += a.i; // Expresion lambda. Equivalente a "a.i = a.i + a.i";
            a.TantoPorCien=70;
            Console.WriteLine(a.i);
            Console.WriteLine(a.TantoPorCien+"%");
            a.Resetea();
            Console.WriteLine(a.TantoPorCien + "%");

            ClaseBasica b = new ClaseBasica();
            b.i =+ b.i; // ojo esto simplemente asigna el signo del entero es decir no hace nada en este caso;
            b.TantoPorCien = 75;
            b.TantoPorCien = 750;
            Console.WriteLine(b.i);
            Console.WriteLine(b.TantoPorCien + "%");

            Console.ReadLine();
        }
    }
}

IC311555Varias cosas a tener en cuenta, hemos ampliado la clase con un nuevo campo llamado “tantoporcien“, además como se trata de un campo que requiere un control sobre su valor ya que queremos que su valor esté comprendido entre 0 y 100, lo declararemos como private de forma que sólo pueda ser manipulado a partir de propiedades (TantoPorCien) o métodos (Resetea).

El resultado será:

20
70%
0%
10
75%

Podemos mejorar nuestra clase implementando un método que imprima directamente en pantalla “Imprime“:

...
namespace ConsoleApplication1
{
    public class ClaseBasica
    {
        ...
        public void Imprime()
        {
            Console.WriteLine(this.i.ToString() + " - " + this.tantoporcien.ToString()+"%");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {          
            object Objeto;
            Objeto = new ClaseBasica();
            ClaseBasica a;
            a = (ClaseBasica)Objeto;
            a.i += a.i; // Expresion lambda. Equivalente a "a.i = a.i + a.i";
            a.TantoPorCien=70;

            a.Imprime();
            a.Resetea();
            a.Imprime();

            ClaseBasica b = new ClaseBasica();
            b.i =+ b.i; // ojo esto simplemente asigna el signo del entero es decir no hace nada en este caso;
            b.TantoPorCien = 75;
            b.TantoPorCien = 750;
            b.Imprime();

            Console.ReadLine();
        }
    }
}

IC310764Propiedades autoimplementadas

Son una característica incluida a partir de la versión 3.0 de C# que hacen más concisa la declaración de una propiedad. Es posible aplicarla siempre que no exista “lógica” en los descriptores de acceso de la propiedad.

public class Cliente {
  private int idcliente;
  public int IdCliente {
    get {return idcliente;}
    set {}
  }
  private string nombre;
  public string Nombre {
    get {return nombre;}
    set {nombre = value;}
  }
}

En el ejemplo anterior tenemos dos propiedades “IdCliente” y “Nombre”, la primera sólo de lectura. Veamos como  quedaría si usáramos propiedades autoimplementadas.

public class Cliente {
  public int IdCliente {get; private set;}
  public string Nombre {get; set;}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{

    public class Cliente
    {
        public int IdCliente { get; private set; }
        public string Nombre { get; set; }
        public Cliente() {
          Nombre ="Vacio";
          IdCliente = 33;

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
         Cliente c1 = new Cliente();
         Console.WriteLine(c1.Nombre);
         Console.WriteLine(c1.IdCliente.ToString());
         c1.Nombre = "Pepe";
         //c1.IdCliente = 66; daría error de compilación porque es private.

         Console.WriteLine(c1.Nombre);
         Console.ReadLine();
        }
    }
}

 

MSDN: Propiedades Autoimplementadas


IC310764

 

 

Los constructores.

Son métodos que llevan el mismo nombre que la clase y se ejecutan automáticamente cuando se crea el objeto. Al igual que otros métodos pueden estar sobrecargados. Veamos un ejemplo de una clase “Alumno” con dos campos, un constructor sobrecargado y un método. No devuelven ningún tipo, ni siquiera “void”. Nunca se le puede llamar de manera explícita en el código, únicamente implícitamente en la creación de una instancia de la clase. El constructor de una clase que no espera parámetro alguno es designado como “constructor por defecto” de la clase. Normalmente un constructor se encarga de la inicialización de campos.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Alumno
    {
        private int edad;
        private string nombre; 
        public Alumno() // Constructor por defecto
        {
            edad = 0;
            nombre = "Sin nombre";
        }
        public Alumno(string p_nombre, int p_edad )
        {
            edad = p_edad;
            nombre = p_nombre;
        }
        public void Imprime()
        {
            Console.WriteLine("Alumno: "+ this.nombre + " - Edad: " + this.edad.ToString()+" años.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {   
            Alumno a = new Alumno();
            Alumno b = new Alumno("Pedro",33);

            a.Imprime();
            b.Imprime();
            Console.ReadLine();
        }
    }
}

El resultado será:

Alumno: Sin nombre - Edad: 0 años.
Alumno: Pedro - Edad: 33 años.

Ojo: si quisiéramos imprimir directamente el nombre o la edad de un alumno por ejemplo “Console.Write (a.nombre);” daría un error ya que se trata de un campo privado.

Otro ejemplo:

...
public class Persona {
   private string elApellido;
   private string elNombre;
   private string laClave;
   public string apellido {
     get { return el Apellido; }
     set { elApellido=value.ToUpper();}
   }
   ... resto de propiedades ...

   // Método constructor por defecto
   public Persona() {
     elApellido="";
     elNombre="";
     laClave="1234";
   }
   // Sobrecarga del método constructor
   public Persona (string apellido, string nombre, string clave) {
     elApellido = apellido;
     elNombre = nombre;
     laClave = clave;
}

IC310764Orden de llamada de los constructores

Cuando se crean objetos de una clase derivada, antes de llamar a su constructor se llama a los constructores de la las clases base, empezando siempre por la más general y terminando por la más específica. Si añadimos en la clase derivada un constructor por defecto, debemos seguir algunas reglas:

  • Si el constructor de una clase derivada no invoca de forma explícita al constructor de la clase base (con la ayuda de la palabra reservada “base“), el constructor por defecto, si existe, lo hará de manera implícita.
  • Si una clase base no ofrece constructor por defecto, la clase derivada debe hace una llamada explícita al constructor de la clase base usando la palabra reservada base.
...
public class ClaseA {
  public ClaseA () { //Constructor por defecto
    Console.WriteLine("Constructor de la clase A");
  }
}

public class ClaseA1: ClaseA {
  public ClaseA1 () { //Constructor por defecto
    Console.WriteLine("Constructor de la clase A1");
  }
}
...
... Main() {
  ClaseA ObjA = new ClaseA();
  ClaseA1 ObjA1 = new ClaseA1();
}

El resultado del programa será:

Constructor de la clase A
Constructor de la clase A
Constructor de la clase A1

Veamos dos ejemplo con la clase “Asalariado” que tendría como  clase base la clase “Persona”, ambos constructores tendrían el mismo efecto ya que la clase Persona sí tiene un constructor por defecto.

public Asalariado(): base() {
  elSalario =0;
}
public Asalariado() {
  elSalario =0;
}

Ahora podríamos añadir un constructor sobrecargado en la clase Asalariado:

public Asalariado (string apellidostring nombre, string clave, decimal salario) {
  elApellido = apellido;
  elNombre = nombre;
  laClave = clave;
  elSalario = salario;
}

Aunque sería más correcto utilizar el constructor que ya existe en la clase Persona:

public Asalariado ( apellido, string nombre, string clave, decmial salario) : base (apellido,nombre,clave) {
  elSalario = salario;
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Persona
    {
        private string elApellido;
        private string elNombre;
        private string laClave;

        // Método constructor por defecto
        public Persona()
        {
            elApellido = "";
            elNombre = "";
            laClave = "1234";
        }

        // Sobrecarga del método constructor
        public Persona(string apellido, string nombre, string clave)
        {
            elApellido = apellido;
            elNombre = nombre;
            laClave = clave;
        }

        public string apellido
        {
            get { return elApellido; }
            set { elApellido = value.ToUpper(); }
        }

    }
    public class Asalariado: Persona {
        private decimal elSalario;
        public Asalariado(): base()
        {
            elSalario = 0;
        }
        /*public Asalariado (string apellido, string nombre, string clave, decimal salario) {
              elApellido = apellido;
              elNombre = nombre;
              laClave = clave;
              elSalario = salario;
        }*/
        public Asalariado ( string apellido, string nombre, string clave, decimal salario) : base (apellido,nombre,clave) {
                elSalario = salario;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Asalariado A1 = new Asalariado("Pedraza", "Juanjo", "1234", 25000);
          Console.ReadLine();
        }
    }
}

IC310765

Más sobre propiedades: “get” y “set”.

Sobre nuestra clase Alumno vamos a crear las propiedades que nos permitan cambiar la edad y el nombre a un objeto ya creado.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{

    public class Alumno
    {
        private int edad;
        private string nombre; 
        public Alumno() // Constructor por defecto
        {
            edad = 0;

        }
        public Alumno(string p_nombre, int p_edad )
        {
            edad = p_edad;
            nombre = p_nombre;
        }
        public int Edad
        {
            get
            {
                return edad;
            }
            set
            {
                edad = value;
            }
        }
        public string Nombre
        {
            get
            {
                return nombre != null ? nombre : "Sin nombre";
            }
        }

        public void Imprime()
        {
            Console.WriteLine("Alumno: "+ this.Nombre + " - Edad: " + this.Edad.ToString()+" años.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {   
            Alumno a = new Alumno();
            Alumno b = new Alumno("Pedro",33);

            a.Imprime();
            b.Imprime();
            Console.ReadLine();
        }
    }
}

El resultado será el mismo que antes.

IC310764Miembros estáticos (static) o compartidos: Se trata de una especie de campo común a todos los objetos de la clase. El siguiente ejemplo muestra como poder llevar un contador de los objetos Alumnos creados, para ello crearemos el campo “contador” y la propiedad “Contador” ambas con la propiedad “static”. Los miembros compartidos o estático son campos, propiedades o métodos a los que pueden acceder todas las instancias de una clase.

Bien muy bien cuando cuando queremos gestionar datos que no son específicos de una instancia de clase sino de la propia clase.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Alumno
    {
        private static int contador;
        private int edad;
        private string nombre;
        private int matricula;
        public static int Contador
        {
            get { return contador; }
        }
        public Alumno() // Constructor por defecto
        {
            edad = 0;
            contador = ++contador;
            matricula = contador;
        }
        public Alumno(string p_nombre, int p_edad )
        {
            edad = p_edad;
            nombre = p_nombre;
            contador = ++contador;
            matricula = contador;
        }
        public int Edad
        {
            get
            {
                return edad;
            }
            set
            {
                edad = value;
            }
        }
        public string Nombre
        {
            get
            {
                return nombre != null ? nombre : "Sin nombre"; // condición ? cierto : falso
            }
        }

        public void Imprime()
        {
            Console.WriteLine("Nro.:"+matricula.ToString()+" de "+Contador.ToString()+" | Alumno: "+ this.Nombre + " - Edad: " + this.Edad.ToString()+" años.");
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Total alumnos antes:" + Alumno.Contador.ToString());
            Alumno a = new Alumno();     
            Alumno b = new Alumno("Pedro",33);
            Console.WriteLine("Total alumnos después:" + Alumno.Contador.ToString());
            a.Imprime();
            b.Imprime();
            Console.ReadLine();
        }
    }
}

La salida sería:

Total alumnos antes:0
Total alumnos después:2
Nro.:1 de 2 | Alumno: Sin nombre - Edad: 0 años.
Nro.:2 de 2 | Alumno: Pedro - Edad: 33 años.

Ojo esto no está permitido:

...
  public static int Contador
        {
            get { return contador; }
            set { contador = value; }
        }
...
a.Contador=100;

Daría error porque la utilización de un miembro compartido mediante un instacia de la clase está prohibida.

 

IC310764

Como acceder a una propiedad de una clase base

A veces un objeto hijo necesita acceder a un campo o propiedad de la clase base de la cual desciende.

Por ejemplo una asignación: ((ClasePadre)ObjetoHijo).Propiedad = Valor;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Padrino
    {
        private string nombre;
        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }
    }
    // Herencia: "Alumno" hereda todo de la clase "Padrino", no obstante los campos declarados como private
    // sólo son accesibles en la clase donde fueron declarados.
    public class Alumno : Padrino
    {
        private static int contador;
        private int edad;
        private string nombre;
        private int matricula;
        public static int Contador
        {
            get { return contador; }
        }
        public Alumno() // Constructor por defecto
        {
            edad = 0;
            contador = ++contador;
            matricula = contador;
        }
        public Alumno(string p_nombre, int p_edad )
        {
            edad = p_edad;
            Nombre = p_nombre;
            contador = ++contador;
            matricula = contador;
        }
        public int Edad
        {
            get
            {
                return edad;
            }
            set
            {
                edad = value;
            }
        }
        // Redefinimos "new"
        public new string Nombre
        {
            get
            {
                return nombre != null ? nombre : "Sin nombre";
            }
            set { nombre = value + ", (alumno)"; }
        }

        public void Imprime()
        {
            Console.WriteLine("Nro.:"+matricula.ToString()+" de "+Contador.ToString()+" | Alumno: "+ this.Nombre + " - Edad: " + this.Edad.ToString()+" años.");
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Total alumnos antes:" + Alumno.Contador.ToString());
            Alumno a = new Alumno();
            a.Nombre = "María";
            ((Padrino)a).Nombre = "Luciana";
            a.Edad = 23;
            Alumno b = new Alumno("Pedro",33);
            ((Padrino)b).Nombre = "Ramón";
            Console.WriteLine("Total alumnos después:" + Alumno.Contador.ToString());
            a.Imprime();
            Console.WriteLine("Padrino: {0}",((Padrino)a).Nombre);
            b.Imprime();
            Console.WriteLine("Padrino: {0}", ((Padrino)b).Nombre);
            Console.ReadLine();
        }
    }
}

El resultado sería:

Total alumnos antes:0
Total alumnos después:2
Nro.:1 de 2 | Alumno: María, (alumno) - Edad: 23 años.
Padrino: Luciana
Nro.:2 de 2 | Alumno: Pedro, (alumno) - Edad: 33 años.
Padrino: Ramón

Destructor de un objeto:

Los destructores son otros métodos particulares de una clase. Llevan el mismo nombre de la clase precedida del signo “~” (Alt+126) y no pueden llevar ningún parámetro, por lo que no puede haber más de un destructor, es decir no permite la sobrecarga.

~Alumno() //Destructor del objeto Alt+126 
 {
   //Libera memoria y cierra archivos abiertos automáticamente
   ... código liberación de memoria ....
   ... código cierre de archivos ...
   ... código cierre de conexión con base de datos ...
 }

Declaración “protected”.

Cuando definimos un campo, propiedad o método como “public”, está será accesible desde cualquier sitio. Si la declaramos como “private” sólo es accesible  en la clase donde está definida, si una clase hereda de otra campos, propiedades o métodos privados, está clase tampoco podrá acceder a los campos, propiedades o métodos de la clase heredada. Para solucionar esto se utiliza la declaración “protected” que permite que una clase “hija” pueda acceder a los métodos de su clase “padre”, sin que estos sean accesibles desde otros sitios.

Arrays de objetos

Muchas veces necesitamos crear más de un objeto de la misma clase para ello, imaginemos un equipo de fútbol, podríamos crear 11 objetos j1, j2, j3 etc. o bien un array de 11 objetos.

... Main()
...
ClaseJugador[] Jugadores = new ClaseJugador[11];
for (byte i = 0; i < 11; i++)
  Jugadores[i] = new ClaseJugador(); // Constructor
...

Ahora bien imaginamos que tenemos la clase: “ClaseJuagador” pero que a partir de ella hemos creado 2 nuevas clases “ClasePortero” y “ClaseJugadorCampo” para este caso también nos serviría un array y podría quedar de la siguiente forma:

... Main()
...
ClaseJugador[] Jugadores = new ClaseJugador[11];
Jugadores[0] = new ClasePortero(); // Constructor
for (byte i = 1; i < 11; i++)
  Jugadores[i] = new ClaseJugadorCampo(); // Constructor
...

Funciones Virtuales: “override”.

Siguiendo con el ejemplo del equipo de futbol, si en vez de un constructor tenemos un método llamado “Jugar”:

public class ClaseJugador {
  public void Jugar() {
    Console.WriteLine("Estoy jugando!");
  }
}

public class ClasePortero: ClaseJugador {
  public new void Jugar() {
    Console.WriteLine("Estoy jugando con las manos!");
  }
}

public class ClaseJugadorCampo: ClaseJugador {
  public new void Jugar() {
    Console.WriteLine("Estoy jugando con los pies!");
  }
}

Si ejecutamos:

...
ClaseJuagador ObjJugador = new ClaseJuagador();
ClasePortero ObjPortero = new ClasePortero();
ClaseJuagadorCampo ObjJugadorCampo = new ClaseJuagadorCampo();
...
ObjJugador.Jugar();
ObjPortero.Jugar();
ObjJugadorCampo.Jugar();
...

Saldrá correctamente:

Estoy juagando!
Estoy jugando con las manos!
Estoy jugando con los pies!

Pero por otro lado si creamos un array:

ClaseJugador[] Jugadores = new ClaseJugador[11];
Jugador[0] = new ClasePortero();
Jugador[1] = new ClaseJugadorCampo();
Jugador[2] = new ClaseJugador();

Jugador[0].Jugar();
Jugador[1].Jugar();
Jugador[2].Jugar();

Todos darán la misma respuesta a pesar de que cada elemento del array es una clase distinta.

ClaseCuadro

Ejemplo de una clase “ClaseCuadro“:
La clase está compuesta por Campos, Propiedades, Métodos y Eventos.

Campos y propiedades: aunque para el usuario de un objeto los campos son similares a las propiedades, estas últimas modifican u obtienen los valores de los campos mediante los descriptores de acceso get y set. Los campos deberán ser declarados como privados (private) si queremos asegurarnos que su valor sea controlado por el código de las propiedades.

Métodos y eventos: Los métodos nos permiten comunicarnos con los objetos que componen una aplicación, pero los objetos también tienen la posibilidad de transmitirnos sus reacciones generando eventos. En el diseño de la interfaz gráfica de una aplicación ya que nos permite obtener datos relativos a las acciones efectuadas por el usuario.

 

 

 

IC588201

OBJETOS

Instanciar las clases.

 

  1. Declaración de una variable que permita el acceso al objeto
  2. Creación del objeto
Persona p; // Paso 1
...
p = new Persona(); // Paso 2
...

Podemos combinar ambas operaciones en una única línea:

Persona p = new Persona();

Para utilizar otro constructor deberemos especificar la lista de parámetros y en función del tipo y número de parámetros el operador new llamará al constructor correspondiente:

Persona p = new Persona("Pedraza","Juanjo","1234");

Inicialización de una instancia a la vez que es creada:

Persona p = new Persona {
  apellido = "Pedraza", 
  nombre="Juanjo", 
  clave="1234"
};

Esto sería lo mismo que hacer:

Persona p;
p = new Persona();
p.apellido = "Pedraza"; 
p.nombre="Juanjo"; 
p.clave="1234";

Inicialización de una colección

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Persona
    {
        public string Apellido;
        public string Nombre;
        public string Clave;
    }

    public class Personilla : Persona
    {
        public int Edad;
    }
    public class Animal
    {
        public string apodo;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Persona p1 = new Persona { Nombre = "Juanjo", Apellido = "Pedraza", Clave = "1234" };
            Animal a1 = new Animal { apodo = "Chucho" };

            // Lista de objetos
            List <Persona> ListaPersonas = new List <Persona> {
                new Persona { Nombre = "Juanjo", Apellido = "Pedraza", Clave = "1234"},
                new Persona { Nombre = "Juan", Apellido = "Martínez", Clave = "1234"},
                new Persona { Nombre = "Carla", Apellido = "López", Clave = "1234"},
                new Persona { Nombre = "Laura", Apellido = "Carreguí", Clave = "1234"},
                new Personilla { Nombre = "Ramón", Apellido = "Ferrandis", Clave = "1234", Edad=33},
            };   

            //Array de Personas
            var ArrayPersonas = new[] {
                new Personilla { Nombre = "Ramón", Apellido = "Ferrandis", Clave = "1234", Edad=33},
                new Persona { Nombre = "Juanjo", Apellido = "Pedraza", Clave = "1234"},
                new Persona { Nombre = "Juan", Apellido = "Martínez", Clave = "1234"},
                new Persona { Nombre = "Carla", Apellido = "López", Clave = "1234"},
                new Persona { Nombre = "Laura", Apellido = "Carreguí", Clave = "1234"}           
            };

            //Array de animales
            var ArrayAnimales = new[] {
                new Animal { apodo = "Chucho" },
                // new Personilla o new Persona daría error porque no pertenece a la clase Animales ...
            };

            //Array de objetos - otra opción
            Persona[] ArrayPersonas2 = {
                new Persona { Nombre = "Juanjo", Apellido = "Pedraza", Clave = "1234"},
                new Persona { Nombre = "Juan", Apellido = "Martínez", Clave = "1234"},
                new Persona { Nombre = "Carla", Apellido = "López", Clave = "1234"},
                new Persona { Nombre = "Laura", Apellido = "Carreguí", Clave = "1234"},
                new Personilla { Nombre = "Ramón", Apellido = "Ferrandis", Clave = "1234", Edad=33}
            };

            Console.WriteLine(p1.Apellido);
            Console.WriteLine(ListaPersonas[1].Apellido);
            Console.WriteLine(ArrayPersonas[2].Apellido);
            Console.WriteLine(ArrayPersonas2[3].Apellido);
            Console.WriteLine(ArrayPersonas2[4].Apellido);

            Console.ReadKey();
        }
    }
}

 

Destrucción de una instancia

La destrucción es automática, el CLR (common Language Runtime) vigila que todas las instancias sean accesibles (es decir que existan para la aplicación), si no encuentra ningún medio para acceder a ella el objeto queda marcado como huérfano y cuando la memoria de la aplicación decrezca entonces el Garbage Collector (Recolector de basura) intervendrá y eliminará los objetos huérfanos. Es durante este proceso cuando los destructores de cada uno de los objetos.

Es posible forzar el Garbage Collector, pero es un trabajo relativamente costoso que hay que utilizar con precaución:

GC.Collect();