Mucho se dice sobre los objetos y la herencia, la verdad es que este concepto es uno de los pilares para la reutilización de componentes.
En la exposición teórica es fácil decir que existe una clase base de la cuál otras se "derivan" formando una jerarquía de clases. Formalmente debemos decir que esa clase base es una Generalización de las otras clases (esto es cuando vamos desde abajo hacia arriba ) y también decimos que las otras clase son una Especialización de la clase base (es cuando vamos desde arriba hacia abajo).
El siguiente esquema muestra el diagrama de clases que representa una simple relación de mascotas:

No hace falta mucha explicación, se trata de una clase que define a una mascota cualquiera y luego tenemos las especializaciones para Gato, Pato y Perro. Hay que observar que cuando de una mascota se trata nosotros (los humanos) acostumbramos a ponerle un nombre; pues bien no importa de qué mascota se trate siempre tiene nombre, eso es un atributo de la clase base (Mascota).
Después de un tiempo le enseñamos a nuestra mascota a presentarse, y en este caso vamos a representar que cada mascota se puede presentar diciendo su nombre que tipo de mascota es y el ruido que normalmente hace. En esta situación es que se observa que como todas las mascotas tienen un nombre es posible poner el código para mostrar el nombre en la clase base y el código para el ruido que cada mascota realiza en cada una de las clases, por esa razón es que el método Presentarse figura en la clase base (superclase) y en las clases derivadas (subclases).
Veamos el código de Mascota.cs
1: using System;
2:
3: namespace Herencia1
4: {
5: /// <summary>
6: /// Clase base
7: /// </summary>
8: public class Mascota
9: {
10: /// <summary>
11: /// Constructor por defecto
12: /// </summary>
13: public Mascota()
14: { }
15: /// <summary>
16: /// Constructor especializado que fija el nombre de la mascota
17: /// </summary>
18: /// <param name="nombre">Nombre de la mascato</param>
19: public Mascota(string nombre)
20: {
21: Nombre = nombre;
22: }
23: /// <summary>
24: /// Mantiene el nombre de la mastaco
25: /// </summary>
26: private string nombre = string.Empty;
27: /// <summary>
28: /// Propiedad para acceder al nombre de la mascota
29: /// No permite la asignación de nombre nulos (operador ??)
30: /// </summary>
31: public string Nombre
32: {
33: get { return nombre; }
34: set { nombre = value ?? string.Empty; }
35: }
36: /// <summary>
37: /// Método utilizado para que la mascota se presente
38: /// </summary>
39: public void Presentarse()
40: {
41: Console.Write("Me llamo {0} ", this.Nombre);
42: Console.Write("soy un {0} ", this.GetType());
43: }
44: }
45: }
Como pueden ver, la clase cuenta con el constructor por defecto (siempre es bueno escribirlo, en especial si hay otros constructores); un constructor especializado que nos permitirá crear objetos del tipo Mascota indicando el nombre de la misma. La estructura interna cuenta con una cadena para almacenar el nombre de la mascota y se implementa una propiedad que facilita el acceso a ese campo, en este caso se muestra un operador interesante que cuenta C# a partir del Framework 3.0 en adelante (vean el manual). Finalmente está un método que permite a cada objeto del tipo Mascota que se presente, éste método muestra el nombre de la mascota y su tipo, GetType() es un mensaje que todos los objetos entienden y devuelve una cadena con su tipo o nombre de clase.
Ahora podemos probar esa clase en Program.cs codificando lo siguiente:
1: using System;
2:
3: namespace Herencia1
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: Mascota miGato = new Mascota("Pulgoso");
10: miGato.Presentarse();
11: }
12: }
13: }
La salida es la siguiente:

Genial, ya logramos que el objeto nos diga su nombre y el tipo. Ahora necesitamos crear objetos de distintos tipos, como ser Perro, Gato o Pato; para ello implementamos las subclases (aquellas que se derivan de Mascota).
Código de Perro.cs
1: using System;
2:
3: namespace Herencia1
4: {
5: /// <summary>
6: /// Especialización de la clase Mascota
7: /// </summary>
8: public class Perro : Mascota
9: {
10: /// <summary>
11: /// Constructor especializado (los constructores no se heredan)
12: /// </summary>
13: /// <param name="nombre">Nombre del perro</param>
14: public Perro(string nombre)
15: : base(nombre) // invola el constructor de la clase base
16: { }
17: /// <summary>
18: /// Método propio para que un perro se presente
19: /// </summary>
20: public void Presentarse()
21: {
22: base.Presentarse();
23: Console.WriteLine("GUAU GUAU");
24: }
25: }
26: }
Observen que al lado del nombre de la clase "Perro" se pone ": Mascota", con eso estamos indicando que la clase Perro se deriva de la clase Mascota.
Una cuestión importante es que los constructores no se heredan todo lo demás si se hereda; por esa razón hace falta codificar un constructor para esta clase y por supuesto vamos a invocar al constructor de la clase base (ver línea 15).
Del mismo modo podemos invocar el método Presentarse() de la clase base (línea 22) y luego mostrar el "ruido" que los objetos de esta clase pueden hacer.
El código de Gato.cs es el siguiente:
1: using System;
2:
3: namespace Herencia1
4: {
5: /// <summary>
6: /// Especialización de la clase Mascota
7: /// </summary>
8: public class Gato : Mascota
9: {
10: /// <summary>
11: /// Constructor especializado (los constructores no se heredan)
12: /// </summary>
13: /// <param name="nombre">Nombre del gato</param>
14: public Gato(string nombre)
15: : base(nombre)
16: { }
17: /// <summary>
18: /// Método propio para que un gato se presente
19: /// </summary>
20: public void Presentarse()
21: {
22: base.Presentarse();
23: Console.WriteLine("MIAU MIAU");
24: }
25: }
26: }
Y el código de Pato.cs es casi lo mismo:
1: using System;
2:
3: namespace Herencia1
4: {
5: /// <summary>
6: /// Especialización de la clase Mascota
7: /// </summary>
8: public class Pato : Mascota
9: {
10: /// <summary>
11: /// Constructor especializado (los constructores no se heredan)
12: /// </summary>
13: /// <param name="nombre">Nombre del pato</param>
14: public Pato(string nombre)
15: : base(nombre)
16: { }
17: /// <summary>
18: /// Método propio para que un pato se presente
19: /// </summary>
20: public void Presentarse()
21: {
22: base.Presentarse();
23: Console.WriteLine("CUAC CUAC");
24: }
25: }
26: }
Ahora podemos probar la creación de distintos objetos para cada subclase, en Program.cs escribimos:
1: using System;
2:
3: namespace Herencia1
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: Perro perro = new Perro("Sultan");
10: Gato gato = new Gato("Garfield");
11: Pato pato = new Pato("Lucas");
12:
13: Console.WriteLine("\nDesde cada uno de los objetos directamente");
14: perro.Presentarse();
15: gato.Presentarse();
16: pato.Presentarse();
17: }
18: }
19: }
La salida es la siguiente:

Logramos lo que queríamos, podemos crear objetos de distintos tipos de mascotas y cuando les pedimos que se presenten reutilizan el código que se encuentra en la clase base.
Bien como los perros, gatos y patos son todos mascotas podríamos necesitar un arreglo de mascotas y pedirle a cada una de las mascotas que están en el arreglo que se presente. Bueno eso no parece tan difícil el código en Program.cs puede ser el siguiente:
1: using System;
2:
3: namespace Herencia1
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: Perro perro = new Perro("Sultan");
10: Gato gato = new Gato("Garfield");
11: Pato pato = new Pato("Lucas");
12:
13: Console.WriteLine("\nDesde cada uno de los objetos directamente");
14: perro.Presentarse();
15: gato.Presentarse();
16: pato.Presentarse();
17:
18: Mascota[] bichos = new Mascota[8];
19: bichos[0] = new Perro("Sultan 2") as Mascota;
20: bichos[1] = new Perro("Diablo") as Mascota;
21: bichos[2] = new Pato("Lucas 2") as Mascota;
22: bichos[3] = new Gato("Silvestre") as Mascota;
23: bichos[4] = new Gato("Midnight") as Mascota;
24: bichos[5] = (Mascota)perro;
25: bichos[6] = (Mascota)gato;
26: bichos[7] = (Mascota)pato;
27:
28: Console.WriteLine("\nDesde un arreglo de objetos sin saber que subtipo es cada uno de ellos");
29: foreach (Mascota bicho in bichos)
30: {
31: bicho.Presentarse();
32: }
33: }
34: }
35: }
En la línea 18 se declara un "arreglo" de objetos del tipo Mascota, luego en cada posición del arreglo se "pone" un objeto realizando la conversión de tipo (casting) desde el tipo específico a Mascota.
Finalmente mediante un ciclo iterativo se le pide a cada elemento del arreglo que se presente (todos son del tipo Mascota en consecuencia entienden el mensaje Presentarse()).
La salida es la siguiente:

UN DESASTRE TOTAL !!!
Resulta que cuando le pedimos a un objeto del tipo Perro que se presente lo hace correctamente y cuando lo hacemos al mismo objeto que está en una de las posiciones del arreglo no lo hace...
Estamos en presencia de un problema ocasionado por lo que se conce como Enlace (binding) estático y dinámico.
Ocurre que en la línea 14 de Program.cs se le pide a un objeto del tipo Perro que se presente, y lo hace correctamente (esto es así porque el enlace es estático y ejecuta el método que está en la clase Perro).
En cambio en la línea 31, cuando le pedimos al objeto que está en el arreglo que se presente, también lo hace correctamente (indudablemente utilizando el enlace estático) pero ahora el objeto es del tipo Mascota (así es como se lo ve).
Para implementar enlace dinámico (esto significa que en tiempo de ejecución el objeto debe determinar de que tipo es y ejecutar el método correspondiente) necesitamos hacer unos cambios.
Primero debemos calificar como "virtual" el método de la clase base que queremos que tenga enlace dinámico, de manera que en la clase Mascota ponemos lo siguiente:
1: /// <summary>
2: /// Método utilizado para que la mascota se presente
3: /// </summary>
4: public virtual void Presentarse()
5: {
6: Console.Write("Me llamo {0} ", this.Nombre);
7: Console.Write("soy un {0} ", this.GetType());
8: }
Esto nos permite sobre escribir el método (es más esto resulta obligatorio, porque sino sigue igual). De manera que en cada clase derivada se debe poner el calificador "override" al método Presentarse()
1: /// <summary>
2: /// Método propio para que un perro se presente
3: /// </summary>
4: public override void Presentarse()
5: {
6: base.Presentarse();
7: Console.WriteLine("GUAU GUAU");
8: }
Pueden probar cambiando solamente en una de las clases derivadas, la salida cuando se cambia en todas es la siguiente:

Ahora si, aún cuando en el arreglo los objetos se manipulan como del tipo Mascota cada uno de ellos reconoce su tipo especializado y ejecuta el método correcto de acuerdo a ello. Esto es lo que se conoce como POLIMORFISMO. Básicamente se trata de tener una colección de objetos, donde cada uno de ellos entiende un mensaje (en este caso Presentarse()) y de acuerdo a su tipo ejecuta el método correcto.
Nota1: No hace falta invocar el método de la clase base, en este ejemplo lo hice porque esa porción de código es común para todas las subclases.
Nota2: En Java la cosa es de otra manera, se puede sobre escribir los métodos sin ninguna calificación especial pero existe un calificador "final" que indica que ese método es el último y no se puede sobre escribir; esto resulta interesante cuando se desea que una método o clase (también se puede aplicar a la clase completa) no pueda ser derivada y consecuentemente alterado su comportamiento. En C# esto se puede lograr con el calificador "sealed".
Nota3: En C# se puede indicar "new" en lugar de "override" con lo cuál se está indicando que el método (el de la clase derivada) mantiene el nombre pero es nuevo; esto tiene otros beneficios que exceden el ejemplo.
Espero que les sirva.
Para leerlo fuera de línea Objetos Herencia y Polimorfismo.pdf (264,12 kb)