Lección 7 · ~14 min · ← Lección 6

Herencia e interfaces

Dos formas de relacionar tipos: herencia ("un Perro ES un Animal — hereda todo lo que tiene") e interfaces ("un Robot PUEDE Hablar() — promete un comportamiento"). Ambas existen para que el código trabaje con muchos tipos a través de una sola forma.

virtual / override: la base permite reemplazar comportamiento

class Animal
{
    public string Nombre;

    public Animal(string nombre)
    {
        Nombre = nombre;
    }

    public virtual void Hablar()          // virtual: los hijos pueden reemplazarlo
    {
        Console.WriteLine(Nombre + " hace un sonido");
    }
}

class Perro : Animal                      // Perro hereda de Animal
{
    public Perro(string nombre) : base(nombre) { }   // base(...): corre el constructor de Animal

    public override void Hablar()          // reemplázalo
    {
        Console.WriteLine(Nombre + " dice guau");
    }
}

Animal a = new Perro("Rex");     // un Perro ES un Animal — la asignación es legal
a.Hablar();                     // el despacho sigue al OBJETO: corre la versión de Perro
Animal b = new Animal("Genérico");
b.Hablar();

Las dos últimas líneas son el punto entero: el tipo de la variable es Animal, pero el MÉTODO elegido es el del objeto real — despacho virtual. Un bucle sobre una colección mixta puede llamar Hablar() a cada uno y obtener el ladrido correcto siempre.

base: construir sobre lo que hizo el padre

base(...) llama al constructor del padre; base.Metodo() llama a la versión del padre — la forma estándar de "extiende, no reemplaces":

class Animal
{
    public virtual void Hablar() { Console.WriteLine("..."); }
}

class Gato : Animal
{
    public override void Hablar()
    {
        base.Hablar();                 // haz lo que hacen los animales…
        Console.WriteLine("miau");     // …luego añade el gato
    }
}

is: "¿realmente es eso?" — con vinculación de variable

class Animal { }
class Perro : Animal
{
    public void Buscar()
    {
        Console.WriteLine("¡buscando!");
    }
}

Animal x = new Perro();        // conversión ascendente: bien, Perro es Animal

if (x is Perro d)              // prueba + declaración en un paso
{
    d.Buscar();                // solo los Perros buscan
}

Su hermano silencioso as convierte y devuelve null si no coincide: Perro dd = x as Perro; — luego comprueba dd != null. Usa is cuando solo vas a ramificar; as cuando vas a seguir usando el valor.

Interfaces: una promesa, no una familia

interface ISaludador
{
    void Saludar();               // un contrato: sin cuerpo, solo la promesa
}

class SaludadorIngles : ISaludador
{
    public void Saludar() { Console.WriteLine("Hello"); }
}

class SaludadorEspanol : ISaludador
{
    public void Saludar() { Console.WriteLine("Hola"); }
}

ISaludador[] saludadores = { new SaludadorIngles(), new SaludadorEspanol() };   // clases sin relación, misma promesa
foreach (ISaludador s in saludadores)
{
    s.Saludar();                  // cada una cumple su propio comportamiento
}

Los dos saludadores no comparten ninguna clase base — la interfaz no es ascendencia, es una promesa común. Por eso C# deja que una clase implemente MUCHAS interfaces pero herede de UNA sola clase base: la familia es exclusiva, las promesas no.

¿Qué herramienta, cuándo?

SituaciónUsa
"X es un tipo de Y" con código compartidoHerencia (class Perro : Animal)
"X puede hacer Z", entre familias distintasInterfaz (: IComparable)
El hijo ajusta el comportamiento del padrevirtual + override
El hijo extiende el comportamiento del padreoverride + base.Metodo()
Bajar de tipo con seguridad antes de usar miembros extrais (ramificar) / as (conservar)

Practica ahora