Ir al contenido principal

Herencia en C#

La herencia es uno de los pilares fundamentales de la programación orientada a objetos y representa una de las características más poderosas de C#. Este mecanismo permite crear nuevas clases basándose en clases existentes, estableciendo relaciones jerárquicas que facilitan la reutilización de código y la creación de estructuras más organizadas y mantenibles.

En el contexto de C#, la herencia permite que una clase (denominada clase derivada o subclase) adquiera automáticamente las propiedades, métodos y otros miembros de otra clase (llamada clase base o superclase). Esta relación se establece mediante la sintaxis de dos puntos (:) y forma la base de muchos patrones de diseño y estructuras de aplicaciones modernas.

Conceptos fundamentales de la herencia

Definición formal de herencia

La herencia es un mecanismo de la programación orientada a objetos que permite definir una nueva clase basándose en una clase existente. La clase derivada hereda automáticamente todos los miembros públicos y protegidos de la clase base, pudiendo además añadir sus propios miembros específicos o modificar el comportamiento de los heredados.

Terminología básica

Término Definición Ejemplo
Clase base La clase de la cual se hereda Animal
Clase derivada La clase que hereda de otra Perro : Animal
Superclase Sinónimo de clase base Animal
Subclase Sinónimo de clase derivada Perro
Jerarquía de herencia Estructura de clases relacionadas Animal -> Mamifero -> Perro

Tipos de herencia en C#

C# admite únicamente herencia simple, lo que significa que una clase puede heredar directamente de una sola clase base. Sin embargo, puede implementar múltiples interfaces, lo que proporciona flexibilidad adicional sin los problemas asociados a la herencia múltiple.

Sintaxis básica de la herencia

Declaración de una clase derivada

// Clase base
public class ClaseBase
{
    // Miembros de la clase base
}

// Clase derivada
public class ClaseDerivada : ClaseBase
{
    // Miembros adicionales de la clase derivada
}

Ejemplo práctico fundamental

// Clase base: Vehiculo
public class Vehiculo
{
    public string Marca { get; set; }
    public string Modelo { get; set; }
    public int Año { get; set; }
    
    public virtual void MostrarInformacion()
    {
        Console.WriteLine($"Vehículo: {Marca} {Modelo} ({Año})");
    }
    
    public virtual void Arrancar()
    {
        Console.WriteLine("El vehículo está arrancando...");
    }
}

// Clase derivada: Coche
public class Coche : Vehiculo
{
    public int NumeroPuertas { get; set; }
    public string TipoCombustible { get; set; }
    
    // Método específico de la clase derivada
    public void AbrirMaletero()
    {
        Console.WriteLine("Abriendo el maletero del coche");
    }
    
    // Sobrescritura de método de la clase base
    public override void MostrarInformacion()
    {
        base.MostrarInformacion(); // Llama al método de la clase base
        Console.WriteLine($"Puertas: {NumeroPuertas}, Combustible: {TipoCombustible}");
    }
}

Niveles de acceso en la herencia

Modificadores de acceso y herencia

Modificador Accesible desde clase derivada Descripción
public ✅ Sí Accesible desde cualquier lugar
protected ✅ Sí Accesible solo desde la clase base y derivadas
internal ✅ Sí (mismo ensamblado) Accesible dentro del mismo ensamblado
private ❌ No Solo accesible desde la clase que lo define
protected internal ✅ Sí Combina protected e internal

Ejemplo de niveles de acceso

public class Empleado
{
    public string Nombre { get; set; }          // Accesible desde cualquier lugar
    protected decimal SalarioBase { get; set; } // Solo accesible desde clases derivadas
    private string NumeroSeguroSocial { get; set; } // Solo accesible desde esta clase
    
    protected virtual decimal CalcularSalario()
    {
        return SalarioBase;
    }
    
    private void ValidarDatos()
    {
        // Método privado, no heredable
    }
}

public class Gerente : Empleado
{
    public decimal Bono { get; set; }
    
    public void AsignarSalario(decimal salario)
    {
        SalarioBase = salario; // ✅ Acceso permitido (protected)
        // NumeroSeguroSocial = "123"; // ❌ Error: no accesible (private)
    }
    
    protected override decimal CalcularSalario()
    {
        return SalarioBase + Bono; // Usa el miembro protected de la clase base
    }
}

Constructores en la herencia

Comportamiento de los constructores

Cuando se crea una instancia de una clase derivada, siempre se ejecuta primero el constructor de la clase base, seguido del constructor de la clase derivada. Este proceso ocurre automáticamente, pero también puede controlarse explícitamente.

Ejemplo de constructores en herencia

public class Animal
{
    public string Especie { get; protected set; }
    
    public Animal()
    {
        Console.WriteLine("Constructor de Animal (sin parámetros)");
        Especie = "Desconocida";
    }
    
    public Animal(string especie)
    {
        Console.WriteLine($"Constructor de Animal con especie: {especie}");
        Especie = especie;
    }
}

public class Perro : Animal
{
    public string Raza { get; set; }
    
    // Constructor sin parámetros
    public Perro()
    {
        Console.WriteLine("Constructor de Perro (sin parámetros)");
        Raza = "Mestizo";
    }
    
    // Constructor que llama al constructor específico de la clase base
    public Perro(string raza) : base("Canino")
    {
        Console.WriteLine($"Constructor de Perro con raza: {raza}");
        Raza = raza;
    }
    
    // Constructor completo
    public Perro(string especie, string raza) : base(especie)
    {
        Console.WriteLine($"Constructor completo de Perro");
        Raza = raza;
    }
}

Uso de constructores

class Program
{
    static void Main()
    {
        Console.WriteLine("=== Creando perro sin parámetros ===");
        var perro1 = new Perro();
        
        Console.WriteLine("\n=== Creando perro con raza ===");
        var perro2 = new Perro("Labrador");
        
        Console.WriteLine("\n=== Creando perro completo ===");
        var perro3 = new Perro("Canino", "Pastor Alemán");
        
        Console.WriteLine($"\nPerro 1: {perro1.Especie} - {perro1.Raza}");
        Console.WriteLine($"Perro 2: {perro2.Especie} - {perro2.Raza}");
        Console.WriteLine($"Perro 3: {perro3.Especie} - {perro3.Raza}");
    }
}

La palabra clave base

Uso de base para acceder a miembros de la clase padre

La palabra clave base permite acceder explícitamente a los miembros de la clase base desde una clase derivada. Es especialmente útil cuando se sobrescriben métodos o se necesita acceder a la funcionalidad original.

public class Figura
{
    protected double X { get; set; }
    protected double Y { get; set; }
    
    public Figura(double x, double y)
    {
        X = x;
        Y = y;
    }
    
    public virtual void MostrarPosicion()
    {
        Console.WriteLine($"Posición: ({X}, {Y})");
    }
    
    public virtual double CalcularArea()
    {
        return 0; // Implementación por defecto
    }
}

public class Rectangulo : Figura
{
    public double Ancho { get; set; }
    public double Alto { get; set; }
    
    public Rectangulo(double x, double y, double ancho, double alto) : base(x, y)
    {
        Ancho = ancho;
        Alto = alto;
    }
    
    public override void MostrarPosicion()
    {
        base.MostrarPosicion(); // Llama al método de la clase base
        Console.WriteLine($"Dimensiones: {Ancho}x{Alto}");
    }
    
    public override double CalcularArea()
    {
        double areaBase = base.CalcularArea(); // Obtiene el valor base
        return Ancho * Alto; // Calcula el área específica
    }
}

Herencia multinivel

Cadenas de herencia

C# permite crear cadenas de herencia donde una clase derivada puede servir como clase base para otra clase, formando una jerarquía multinivel.

// Nivel 1: Clase base
public class Ser
{
    public bool EstaVivo { get; protected set; }
    
    public Ser()
    {
        EstaVivo = true;
    }
    
    public virtual void Respirar()
    {
        Console.WriteLine("El ser está respirando");
    }
}

// Nivel 2: Primera derivación
public class Animal : Ser
{
    public string Habitat { get; set; }
    
    public virtual void Moverse()
    {
        Console.WriteLine("El animal se está moviendo");
    }
    
    public override void Respirar()
    {
        Console.WriteLine("El animal respira oxígeno");
    }
}

// Nivel 3: Segunda derivación
public class Mamifero : Animal
{
    public bool TienePelo { get; set; }
    
    public virtual void Amamantar()
    {
        Console.WriteLine("El mamífero está amamantando");
    }
    
    public override void Moverse()
    {
        Console.WriteLine("El mamífero camina sobre sus patas");
    }
}

// Nivel 4: Tercera derivación
public class Gato : Mamifero
{
    public string ColorPelaje { get; set; }
    
    public void Maullar()
    {
        Console.WriteLine("El gato está maullando");
    }
    
    public override void Moverse()
    {
        base.Moverse(); // Llama al método del mamífero
        Console.WriteLine("El gato se mueve sigilosamente");
    }
}

Ejemplo de uso de herencia multinivel

class Program
{
    static void Main()
    {
        var gato = new Gato
        {
            Habitat = "Doméstico",
            TienePelo = true,
            ColorPelaje = "Gris"
        };
        
        Console.WriteLine("=== Propiedades heredadas ===");
        Console.WriteLine($"Está vivo: {gato.EstaVivo}");     // De Ser
        Console.WriteLine($"Hábitat: {gato.Habitat}");        // De Animal
        Console.WriteLine($"Tiene pelo: {gato.TienePelo}");   // De Mamifero
        Console.WriteLine($"Color: {gato.ColorPelaje}");      // De Gato
        
        Console.WriteLine("\n=== Métodos heredados y sobrescritos ===");
        gato.Respirar();    // Sobrescrito en Animal
        gato.Moverse();     // Sobrescrito en Gato, llama a Mamifero
        gato.Amamantar();   // De Mamifero
        gato.Maullar();     // Específico de Gato
    }
}

Limitaciones de la herencia

La palabra clave sealed

La palabra clave sealed impide que una clase sea utilizada como clase base, terminando efectivamente la cadena de herencia.

// Clase que puede ser heredada
public class Vehiculo
{
    public string Marca { get; set; }
}

// Clase sellada - no puede ser heredada
public sealed class VehiculoEspecial : Vehiculo
{
    public string CaracteristicaEspecial { get; set; }
}

// La siguiente declaración causaría un error de compilación
// public class SuperVehiculo : VehiculoEspecial { } // ❌ Error

Ejemplo práctico completo

// Sistema de gestión de empleados con herencia
public class Persona
{
    public string Nombre { get; set; }
    public string Apellidos { get; set; }
    public DateTime FechaNacimiento { get; set; }
    
    public virtual void MostrarInformacion()
    {
        Console.WriteLine($"Nombre: {Nombre} {Apellidos}");
        Console.WriteLine($"Fecha de nacimiento: {FechaNacimiento:dd/MM/yyyy}");
    }
    
    public int ObtenerEdad()
    {
        return DateTime.Now.Year - FechaNacimiento.Year;
    }
}

public class EmpleadoBase : Persona
{
    public string NumeroEmpleado { get; set; }
    public DateTime FechaContratacion { get; set; }
    protected decimal SalarioBase { get; set; }
    
    public EmpleadoBase(string numeroEmpleado, decimal salarioBase)
    {
        NumeroEmpleado = numeroEmpleado;
        SalarioBase = salarioBase;
        FechaContratacion = DateTime.Now;
    }
    
    public virtual decimal CalcularSalario()
    {
        return SalarioBase;
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Número de empleado: {NumeroEmpleado}");
        Console.WriteLine($"Fecha de contratación: {FechaContratacion:dd/MM/yyyy}");
        Console.WriteLine($"Salario: {CalcularSalario():C}");
    }
}

public sealed class Gerente : EmpleadoBase
{
    public decimal BonoGerencial { get; set; }
    public int EmpleadosACargo { get; set; }
    
    public Gerente(string numeroEmpleado, decimal salarioBase, decimal bono) 
        : base(numeroEmpleado, salarioBase)
    {
        BonoGerencial = bono;
    }
    
    public override decimal CalcularSalario()
    {
        return base.CalcularSalario() + BonoGerencial;
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Empleados a cargo: {EmpleadosACargo}");
        Console.WriteLine($"Bono gerencial: {BonoGerencial:C}");
    }
}

// Ejemplo de uso
class Program
{
    static void Main()
    {
        var gerente = new Gerente("GER001", 50000, 15000)
        {
            Nombre = "Ana",
            Apellidos = "García López",
            FechaNacimiento = new DateTime(1980, 5, 15),
            EmpleadosACargo = 12
        };
        
        Console.WriteLine("=== Información del Gerente ===");
        gerente.MostrarInformacion();
        Console.WriteLine($"Edad: {gerente.ObtenerEdad()} años");
    }
}

Resumen

La herencia en C# es un mecanismo fundamental que permite crear nuevas clases basándose en clases existentes, facilitando la reutilización de código y la creación de jerarquías lógicas. Hemos explorado cómo una clase derivada hereda automáticamente los miembros públicos y protegidos de su clase base, puede añadir funcionalidad específica y sobrescribir comportamientos existentes.

Los conceptos clave incluyen el uso de la sintaxis de dos puntos para establecer la herencia, la comprensión de los niveles de acceso y su impacto en la herencia, el manejo adecuado de constructores en cadenas de herencia, y el uso de la palabra clave base para acceder a funcionalidad de la clase padre. La herencia multinivel permite crear jerarquías complejas, mientras que sealed proporciona control sobre qué clases pueden ser heredadas. Estos fundamentos preparan el terreno para conceptos más avanzados como el polimorfismo y las clases abstractas.