Al cerrar la lección anterior quedó pendiente un problema: Libro ya tiene Titulo, Autor, Disponible, Prestar(), Devolver() y MostrarFicha() perfectamente definidos, pero si BiblioTech quisiera incorporar también revistas al catálogo, tendrías que crear una clase Revista copiando y pegando exactamente esas mismas propiedades y métodos, cambiando solo lo específico de una revista (por ejemplo, su número de edición). Esa duplicación es justo lo que la herencia evita: permite definir una clase base con lo que varios tipos tienen en común, y que otras clases hereden automáticamente esas propiedades y métodos, añadiendo solo lo que las distingue. En esta lección extraerás de Libro una clase base, MaterialBibliotecario, de la que heredarán tanto Libro como una nueva clase Revista.

Contenido

  1. El problema: propiedades y métodos duplicados
  2. Heredar con : — la clase base MaterialBibliotecario
  3. Libro hereda de MaterialBibliotecario
  4. El operador base para llamar al constructor de la clase base
  5. Añadiendo Revista como segunda clase heredera
  6. Sobreescribir miembros heredados con virtual y override
  7. Clases selladas con sealed
  8. Jerarquía de una sola herencia en C#

  1. El problema: propiedades y métodos duplicados

Recupera la clase Libro tal y como quedó en la lección anterior:

class Libro
{
    public string Titulo { get; set; }
    public string Autor { get; set; }
    public string Isbn { get; set; }
    public bool Disponible { get; set; } = true;

    public Libro(string titulo, string autor, string isbn)
    {
        Titulo = titulo;
        Autor = autor;
        Isbn = isbn;
    }

    public void Prestar() { /* ... */ }
    public void Devolver() { /* ... */ }
    public void MostrarFicha() { /* ... */ }
}

Si ahora escribieras una clase Revista desde cero, con Titulo, Autor, Disponible, Prestar(), Devolver() y MostrarFicha() prácticamente idénticos, y solo Isbn sustituido por, por ejemplo, NumeroEdicion, estarías duplicando una cantidad considerable de código. Y lo que es peor: si más adelante corriges un error en Prestar(), tendrías que acordarte de corregirlo también en la copia de Revista. La herencia resuelve este problema de raíz.

  1. Heredar con : — la clase base MaterialBibliotecario

El primer paso es identificar qué tienen en común un libro y una revista dentro de BiblioTech: ambos tienen un título, un autor, y pueden estar disponibles o prestados. Esa parte común se extrae a una nueva clase, MaterialBibliotecario, que actuará como clase base:

class MaterialBibliotecario
{
    public string Titulo { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; set; } = true;

    public MaterialBibliotecario(string titulo, string autor)
    {
        Titulo = titulo;
        Autor = autor;
    }

    public void Prestar()
    {
        if (Disponible)
        {
            Disponible = false;
            Console.WriteLine($"'{Titulo}' ha sido prestado.");
        }
        else
        {
            Console.WriteLine($"'{Titulo}' no esta disponible para prestamo.");
        }
    }

    public void Devolver()
    {
        Disponible = true;
        Console.WriteLine($"'{Titulo}' ha sido devuelto.");
    }

    public void MostrarFicha()
    {
        Console.WriteLine($"Titulo: {Titulo}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }
}

MaterialBibliotecario no es un concepto que vaya a usarse "en solitario" en BiblioTech —nunca crearás un objeto que sea "un material bibliotecario" a secas, sin más—, sino la base sobre la que se construyen conceptos más concretos como Libro o Revista. Esta idea se retomará en la lección de Abstracción, más adelante en el módulo.

  1. Libro hereda de MaterialBibliotecario

Para que una clase herede de otra, se escribe dos puntos (:) después del nombre de la clase, seguido del nombre de la clase base:

class Libro : MaterialBibliotecario
{
    public string Isbn { get; set; }

    public Libro(string titulo, string autor, string isbn) : base(titulo, autor)
    {
        Isbn = isbn;
    }
}

class Libro : MaterialBibliotecario significa "Libro es un MaterialBibliotecario, además de tener sus propias particularidades". Gracias a la herencia, Libro obtiene automáticamente todas las propiedades (Titulo, Autor, Disponible) y métodos (Prestar(), Devolver(), MostrarFicha()) de MaterialBibliotecario, sin volver a escribirlos, y solo necesita declarar lo que le es propio: Isbn.

Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

Console.WriteLine(libro1.Titulo);     // Rayuela (heredado de MaterialBibliotecario)
Console.WriteLine(libro1.Isbn);       // 978-84-376-0495-4 (propio de Libro)
libro1.Prestar();                     // 'Rayuela' ha sido prestado. (heredado)
libro1.MostrarFicha();                // heredado, muestra Titulo/Autor/Disponible

  1. El operador base para llamar al constructor de la clase base

En el constructor de Libro del apartado anterior aparece : base(titulo, autor) después de la firma del constructor. La palabra clave base permite invocar explícitamente un miembro de la clase base —en este caso, su constructor— desde la clase heredera.

public Libro(string titulo, string autor, string isbn) : base(titulo, autor)
{
    Isbn = isbn;
}

Esto es necesario porque MaterialBibliotecario exige titulo y autor en su propio constructor (no tiene un constructor sin parámetros), así que cualquier clase que herede de ella debe indicar explícitamente cómo se construye esa parte heredada. El orden de ejecución es siempre: primero se ejecuta el constructor de la clase base (MaterialBibliotecario(titulo, autor), que asigna Titulo y Autor), y después el cuerpo del constructor de Libro (que asigna Isbn).

base también se puede usar dentro de un método para invocar la versión de la clase base de ese mismo método (lo verás en el apartado 6, con MostrarFicha()), no solo en constructores.

  1. Añadiendo Revista como segunda clase heredera

Con MaterialBibliotecario ya definida, añadir Revista al catálogo de BiblioTech es ahora mucho más sencillo: solo hace falta lo que la distingue de un libro, que en este caso será su número de edición.

class Revista : MaterialBibliotecario
{
    public int NumeroEdicion { get; set; }

    public Revista(string titulo, string autor, int numeroEdicion) : base(titulo, autor)
    {
        NumeroEdicion = numeroEdicion;
    }
}
Revista revista1 = new Revista("National Geographic", "Varios autores", 302);

revista1.MostrarFicha();  // heredado: Titulo, Autor, Disponible
Console.WriteLine(revista1.NumeroEdicion); // 302 (propio de Revista)
revista1.Prestar();       // heredado, funciona igual que en Libro

Libro y Revista comparten toda la lógica de Titulo, Autor, Disponible, Prestar() y Devolver() sin haberla escrito dos veces: ambas heredan de la misma clase base MaterialBibliotecario, y cada una añade únicamente su particularidad (Isbn en un caso, NumeroEdicion en el otro).

  1. Sobreescribir miembros heredados con virtual y override

Por defecto, un método heredado se comporta exactamente igual en la clase heredera que en la clase base: libro1.MostrarFicha() y revista1.MostrarFicha() ejecutan literalmente el mismo código de MaterialBibliotecario. Pero tiene sentido que la ficha de un libro incluya también su ISBN, y la de una revista, su número de edición —información que MaterialBibliotecario no conoce. Para permitir que una clase heredera sobreescriba el comportamiento de un método heredado, la clase base debe marcar ese método como virtual, y la heredera debe marcar su propia versión como override:

class MaterialBibliotecario
{
    // ... propiedades y constructor como antes ...

    public virtual void MostrarFicha()
    {
        Console.WriteLine($"Titulo: {Titulo}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }
}

class Libro : MaterialBibliotecario
{
    public string Isbn { get; set; }

    public Libro(string titulo, string autor, string isbn) : base(titulo, autor)
    {
        Isbn = isbn;
    }

    public override void MostrarFicha()
    {
        base.MostrarFicha();               // ejecuta primero la version de MaterialBibliotecario
        Console.WriteLine($"ISBN: {Isbn}"); // y anade la linea propia de Libro
    }
}
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
libro1.MostrarFicha();
// Titulo: Rayuela
// Autor: Julio Cortazar
// Disponible: True
// ISBN: 978-84-376-0495-4

Dentro de Libro.MostrarFicha(), la llamada base.MostrarFicha() ejecuta la versión original del método definida en MaterialBibliotecario (las tres primeras líneas), y a continuación se añade la línea específica del ISBN. Esto evita duplicar las líneas de Titulo, Autor y Disponible: se reutiliza la implementación de la clase base y solo se añade lo nuevo. Revista haría exactamente lo mismo con su propio NumeroEdicion:

class Revista : MaterialBibliotecario
{
    public int NumeroEdicion { get; set; }

    public Revista(string titulo, string autor, int numeroEdicion) : base(titulo, autor)
    {
        NumeroEdicion = numeroEdicion;
    }

    public override void MostrarFicha()
    {
        base.MostrarFicha();
        Console.WriteLine($"Numero de edicion: {NumeroEdicion}");
    }
}
Palabra clave Dónde se usa Qué significa
virtual En el método de la clase base "Las clases herederas pueden sobreescribir este método"
override En el método de la clase heredera "Esta es una versión propia de un método marcado virtual en la base"
base.Metodo() Dentro de un método sobreescrito "Ejecuta también la versión original del método, definida en la clase base"

Esta lección se ha centrado en la mecánica de heredar y sobreescribir; en la próxima lección, Polimorfismo, verás por qué esta capacidad de sobreescribir métodos es mucho más potente de lo que parece a primera vista, cuando se combina con listas y arrays de la clase base.

  1. Clases selladas con sealed

A veces conviene impedir explícitamente que una clase tenga herederas, por ejemplo porque su diseño no está pensado para extenderse más. Para ello se usa la palabra clave sealed antes de class:

sealed class Revista : MaterialBibliotecario
{
    public int NumeroEdicion { get; set; }

    public Revista(string titulo, string autor, int numeroEdicion) : base(titulo, autor)
    {
        NumeroEdicion = numeroEdicion;
    }

    public override void MostrarFicha()
    {
        base.MostrarFicha();
        Console.WriteLine($"Numero de edicion: {NumeroEdicion}");
    }
}

// class RevistaDigital : Revista { }
// Error de compilacion: no se puede heredar de una clase sellada ('sealed')

Marcar Revista como sealed deja claro, para quien lea el código, que no está pensada como una nueva clase base: es un punto final en esa rama de la jerarquía. MaterialBibliotecario y Libro, en cambio, se mantienen sin sealed durante el resto del módulo, ya que MaterialBibliotecario necesita seguir siendo heredable por diseño.

  1. Jerarquía de una sola herencia en C#

Una limitación importante —y deliberada— de C# es que una clase solo puede heredar de una clase base: no existe la herencia múltiple de clases, a diferencia de otros aspectos del lenguaje (como las interfaces, que sí se pueden combinar varias a la vez, y que verás en el Módulo 4). Esto significa que la jerarquía de herencia de BiblioTech forma siempre un árbol, nunca una red:

classDiagram
    MaterialBibliotecario <|-- Libro
    MaterialBibliotecario <|-- Revista
    class MaterialBibliotecario {
        +string Titulo
        +string Autor
        +bool Disponible
        +Prestar()
        +Devolver()
        +MostrarFicha()
    }
    class Libro {
        +string Isbn
        +MostrarFicha()
    }
    class Revista {
        +int NumeroEdicion
        +MostrarFicha()
    }

Todas las clases en C# —incluida MaterialBibliotecario, aunque no lo hayas escrito explícitamente— heredan, en última instancia, de una clase raíz común llamada object, de la que provienen métodos como ToString() que ya has usado indirectamente al interpolar objetos en cadenas de texto. Esto queda fuera del alcance de esta lección, pero conviene saber que la jerarquía de clases de C# siempre tiene, en la cima, esa raíz común.

Errores Comunes y Consejos

  • Olvidar base(...) cuando la clase base no tiene constructor sin parámetros: si MaterialBibliotecario solo define MaterialBibliotecario(string titulo, string autor), cualquier clase heredera está obligada a indicar : base(...) con esos argumentos; si no, el compilador da un error.
  • Sobreescribir sin override (o sin virtual en la base): si el método de la clase base no es virtual, la clase heredera no puede usar override; y si lo que se busca es ocultar el método con otro nombre distinto sin relación real de sobreescritura (usando new en la firma del método), el comportamiento resultante suele confundir y rara vez es lo que se pretendía. Usa siempre virtual/override cuando la intención sea sobreescribir.
  • Duplicar código que ya está en la clase base: si Libro y Revista vuelven a definir Titulo, Autor o Disponible por su cuenta, se pierde la ventaja de la herencia y se reintroduce el problema que esta lección resuelve.
  • Marcar sealed una clase que sí necesitas seguir extendiendo: sealed es una decisión de diseño deliberada; aplícalo solo cuando tengas la certeza de que esa clase no debe tener más herederas (como Revista en este módulo), no como costumbre.
  • Esperar herencia múltiple de clases: en C# una clase solo puede heredar de una única clase base; si necesitas combinar varias capacidades independientes, la herramienta adecuada son las interfaces, que verás en el Módulo 4.

Ejercicios

  1. Define la clase MaterialBibliotecario con las propiedades Titulo y Autor (string) y Disponible (bool, con inicializador de propiedad a true), un constructor MaterialBibliotecario(string titulo, string autor), y un método void MostrarFicha() que muestre las tres propiedades por consola.

  2. Crea la clase Libro : MaterialBibliotecario con la propiedad adicional Isbn (string) y un constructor Libro(string titulo, string autor, string isbn) que use base(...) para inicializar la parte heredada. Crea un objeto Libro y muestra su Titulo (heredado) y su Isbn (propio).

  3. Crea la clase Revista : MaterialBibliotecario (sin sealed por ahora) con la propiedad NumeroEdicion (int) y su propio constructor con base(...). Marca MostrarFicha() como virtual en MaterialBibliotecario y sobreescríbelo en Revista con override, de forma que, además de la ficha heredada, muestre también el número de edición usando base.MostrarFicha().

Soluciones

class MaterialBibliotecario
{
    public string Titulo { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; set; } = true;

    public MaterialBibliotecario(string titulo, string autor)
    {
        Titulo = titulo;
        Autor = autor;
    }

    public void MostrarFicha()
    {
        Console.WriteLine($"Titulo: {Titulo}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }
}
class Libro : MaterialBibliotecario
{
    public string Isbn { get; set; }

    public Libro(string titulo, string autor, string isbn) : base(titulo, autor)
    {
        Isbn = isbn;
    }
}

Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(libro1.Titulo); // Rayuela
Console.WriteLine(libro1.Isbn);   // 978-84-376-0495-4
class MaterialBibliotecario
{
    public string Titulo { get; set; }
    public string Autor { get; set; }
    public bool Disponible { get; set; } = true;

    public MaterialBibliotecario(string titulo, string autor)
    {
        Titulo = titulo;
        Autor = autor;
    }

    public virtual void MostrarFicha()
    {
        Console.WriteLine($"Titulo: {Titulo}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Disponible: {Disponible}");
    }
}

class Revista : MaterialBibliotecario
{
    public int NumeroEdicion { get; set; }

    public Revista(string titulo, string autor, int numeroEdicion) : base(titulo, autor)
    {
        NumeroEdicion = numeroEdicion;
    }

    public override void MostrarFicha()
    {
        base.MostrarFicha();
        Console.WriteLine($"Numero de edicion: {NumeroEdicion}");
    }
}

Revista revista1 = new Revista("National Geographic", "Varios autores", 302);
revista1.MostrarFicha();
// Titulo: National Geographic
// Autor: Varios autores
// Disponible: True
// Numero de edicion: 302

Conclusión

En esta lección has aprendido a evitar la duplicación de código extrayendo una clase base, MaterialBibliotecario, de la que heredan Libro y la nueva Revista mediante :; has usado base para invocar el constructor (y el método) de la clase base, has sobreescrito miembros heredados con virtual/override, has visto cómo sealed impide que una clase tenga herederas, y por qué C# solo permite heredar de una única clase base a la vez. El modelo de dominio de BiblioTech ahora tiene una jerarquía real: MaterialBibliotecario en la base, con Libro y Revista como especializaciones concretas.

Sin embargo, todavía no has explotado la verdadera potencia de esta jerarquía: poder tratar un Libro y una Revista de forma uniforme, como si ambos fueran simplemente "un MaterialBibliotecario", y dejar que cada uno se comporte según su propio tipo en tiempo de ejecución. Eso es exactamente el polimorfismo, el tema de la próxima lección.

Curso de Programación en C#

Módulo 1: Introducción a C#

Módulo 2: Estructuras de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Conceptos Avanzados de C#

Módulo 5: Trabajando con Datos

Módulo 6: Temas Avanzados

Módulo 7: Construcción de Aplicaciones

Módulo 8: Mejores Prácticas y Patrones de Diseño

Módulo 9: Proyecto Final

© Copyright 2026. Todos los derechos reservados