En la lección anterior, cada vez que creabas un objeto Libro tenías que acordarte de asignar sus cuatro propiedades a mano (Titulo, Autor, Isbn, Disponible) o usar un inicializador de objeto; si olvidabas Disponible, el libro nacía con false (el valor por defecto de bool) en lugar de true, sin que nada te avisara del error. Esta lección resuelve ese problema con los constructores: un método especial que se ejecuta automáticamente en el instante en que se crea un objeto con new, garantizando que nazca siempre en un estado coherente. De paso, conocerás su contrapartida —los destructores— y ampliarás el modelo de dominio de BiblioTech con dos piezas nuevas: Socio y Prestamo, que junto con Libro formarán el núcleo sobre el que se construirá el resto del curso.

Contenido

  1. Qué es un constructor y cuándo se ejecuta
  2. El constructor por defecto
  3. Constructores con parámetros
  4. Sobrecarga de constructores
  5. Encadenar constructores con this(...)
  6. Inicializadores de propiedades
  7. Creando la clase Socio
  8. Uniendo piezas: la clase Prestamo
  9. Destructores y una mención a IDisposable

  1. Qué es un constructor y cuándo se ejecuta

Un constructor es un método especial de una clase que se ejecuta automáticamente cada vez que se crea un objeto con new. Se distingue de un método normal en dos aspectos: tiene exactamente el mismo nombre que la clase, y no declara ningún tipo de retorno (ni siquiera void).

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

    public Libro()
    {
        Disponible = true;
        Console.WriteLine("Se ha creado un nuevo libro.");
    }
}
Libro libro1 = new Libro(); // ejecuta el constructor: imprime el mensaje y pone Disponible = true
Console.WriteLine(libro1.Disponible); // True

En cuanto se ejecuta new Libro(), C# reserva memoria para el nuevo objeto y, a continuación, ejecuta automáticamente el código del constructor —en este caso, asigna Disponible = true y muestra un mensaje— antes de devolver la referencia al objeto ya creado. Nunca se llama a un constructor "a mano" como si fuera un método cualquiera (libro1.Libro() sería un error de compilación); solo se ejecuta como parte de new.

  1. El constructor por defecto

Si una clase no define ningún constructor propio, C# le proporciona automáticamente uno implícito, sin parámetros, que no hace nada más que crear el objeto con todas sus propiedades en su valor por defecto (como viste en la lección anterior). Es lo que ha estado ocurriendo, sin que lo supieras, en todos los new Libro() de las lecciones previas a esta.

En cuanto defines cualquier constructor propio (como el del apartado anterior), ese constructor implícito y gratuito desaparece: si sigues necesitando poder crear un Libro sin argumentos, tendrás que escribir tú mismo ese constructor sin parámetros, de forma explícita.

  1. Constructores con parámetros

Lo habitual es que un constructor reciba parámetros para inicializar el objeto con datos concretos desde el primer momento, en lugar de crear un objeto "vacío" y rellenarlo después:

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

    public Libro(string titulo, string autor, string isbn)
    {
        Titulo = titulo;
        Autor = autor;
        Isbn = isbn;
        Disponible = true;
    }
}
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");

Console.WriteLine(libro1.Titulo);      // Rayuela
Console.WriteLine(libro1.Disponible);  // True (se fija siempre en el constructor)

Este constructor garantiza dos cosas a la vez: que no se pueda crear un Libro sin indicar su título, autor e ISBN (son parámetros obligatorios), y que Disponible siempre empiece en true sin que quien crea el libro tenga que acordarse de asignarlo. Fíjate en que, dentro del constructor, los nombres de los parámetros (titulo, en minúscula) son distintos de los nombres de las propiedades (Titulo, en mayúscula) precisamente para poder distinguirlos con claridad al escribir Titulo = titulo;.

  1. Sobrecarga de constructores

Igual que los métodos, los constructores se pueden sobrecargar: una misma clase puede tener varios constructores, siempre que se distingan por su lista de parámetros.

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

    public Libro()
    {
        Titulo = "Titulo desconocido";
        Autor = "Autor desconocido";
        Isbn = "";
        Disponible = true;
    }

    public Libro(string titulo, string autor, string isbn)
    {
        Titulo = titulo;
        Autor = autor;
        Isbn = isbn;
        Disponible = true;
    }
}
Libro libroSinDatos = new Libro();                                       // usa el constructor sin parametros
Libro libroCompleto = new Libro("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1"); // usa el constructor con 3 parametros

C# elige automáticamente qué constructor ejecutar según el número y tipo de argumentos que le pases en el new, exactamente igual que con la sobrecarga de métodos vista en la lección anterior.

  1. Encadenar constructores con this(...)

El ejemplo anterior repite Disponible = true; en los dos constructores: si algún día cambiara esa lógica de inicialización, habría que recordar actualizarla en los dos sitios. Para evitar esta duplicación, un constructor puede llamar a otro constructor de la misma clase usando this(...), delegando en él la inicialización común:

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

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

    public Libro() : this("Titulo desconocido", "Autor desconocido", "")
    {
        // El cuerpo puede quedar vacio: this(...) ya ha hecho el trabajo
    }
}

La sintaxis : this(...) se coloca entre la firma del constructor y su cuerpo ({ }), e indica "antes de ejecutar este constructor, ejecuta primero el otro constructor con estos argumentos". Así, new Libro() termina ejecutando también la lógica del constructor de tres parámetros, con valores por defecto para cada uno, sin duplicar la asignación de Disponible. Esta técnica se conoce como encadenamiento de constructores, y es la forma recomendada de evitar repetir lógica de inicialización cuando una clase tiene varios constructores.

  1. Inicializadores de propiedades

Para propiedades que siempre deben arrancar con el mismo valor fijo, independientemente del constructor que se use, C# ofrece una alternativa aún más directa: asignar el valor por defecto en la propia declaración de la propiedad, sin necesidad de tocar ningún constructor:

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

    public Libro(string titulo, string autor, string isbn)
    {
        Titulo = titulo;
        Autor = autor;
        Isbn = isbn;
        // Ya no hace falta asignar Disponible aqui: siempre empieza en true
    }
}

Un inicializador de propiedad se ejecuta antes que el cuerpo de cualquier constructor, así que si el constructor asigna después un valor distinto a esa misma propiedad, el valor del constructor prevalece: el inicializador solo actúa como valor de partida. Es la forma más simple y legible de fijar un valor por defecto cuando ese valor no depende de ningún parámetro del constructor.

  1. Creando la clase Socio

Con los constructores ya dominados, es el momento de añadir la segunda pieza del modelo de BiblioTech: la clase Socio, que representa a una persona registrada en la biblioteca con derecho a pedir libros prestados.

class Socio
{
    public int Id { get; set; }
    public string Nombre { get; set; }

    public Socio(int id, string nombre)
    {
        Id = id;
        Nombre = nombre;
    }

    public void MostrarFicha()
    {
        Console.WriteLine($"Socio #{Id}: {Nombre}");
    }
}
Socio socio1 = new Socio(1, "Ana Martinez");
Socio socio2 = new Socio(2, "Luis Gomez");

socio1.MostrarFicha(); // Socio #1: Ana Martinez
socio2.MostrarFicha(); // Socio #2: Luis Gomez

Socio sigue exactamente el mismo patrón que Libro: propiedades autoimplementadas (Id, Nombre), un constructor que exige los datos esenciales al crearlo, y un método (MostrarFicha()) que aprovecha esos datos. Id identificará a cada socio de forma única dentro de BiblioTech —una idea que cobrará más importancia en el Módulo 5, al persistir datos en una base de datos.

  1. Uniendo piezas: la clase Prestamo

Con Libro y Socio ya definidos, BiblioTech necesita representar el concepto que los relaciona: un préstamo, que vincula un libro concreto con el socio que lo ha tomado prestado, junto con las fechas relevantes. Esta es la tercera y última pieza del modelo de dominio del curso:

class Prestamo
{
    public Libro Libro { get; set; }
    public Socio Socio { get; set; }
    public DateTime FechaPrestamo { get; set; }
    public DateTime? FechaDevolucion { get; set; } // null mientras el libro no se ha devuelto

    public Prestamo(Libro libro, Socio socio)
    {
        Libro = libro;
        Socio = socio;
        FechaPrestamo = DateTime.Now;
        FechaDevolucion = null;
    }

    public void MostrarFicha()
    {
        string estado = FechaDevolucion is null ? "en curso" : $"devuelto el {FechaDevolucion:d}";
        Console.WriteLine($"Prestamo de '{Libro.Titulo}' a {Socio.Nombre} el {FechaPrestamo:d} ({estado})");
    }
}

Fíjate en tres detalles importantes de esta clase:

  • Sus propiedades Libro y Socio no son tipos primitivos, sino objetos de otras clases que ya has definido: un Prestamo no duplica el título del libro ni el nombre del socio, sino que guarda una referencia a los objetos Libro y Socio originales (recuerda que las clases son tipos por referencia). Si más adelante ese libro se marca como no disponible, el Prestamo que lo referencia "ve" automáticamente el cambio, porque apunta al mismo objeto.
  • FechaDevolucion es de tipo DateTime? (con ?): el signo de interrogación indica un tipo que admite null (nullable), necesario aquí porque, mientras el préstamo sigue en curso, todavía no existe ninguna fecha de devolución real.
  • El constructor fija FechaPrestamo al momento actual (DateTime.Now) automáticamente: quien crea un Prestamo no tiene que calcular ni pasar la fecha a mano.

Así se usan las tres clases juntas, tal y como se comportarán durante el resto del curso:

Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Socio socio1 = new Socio(1, "Ana Martinez");

libro1.Prestar(); // el libro pasa a Disponible = false (metodo visto en la leccion anterior)
Prestamo prestamo1 = new Prestamo(libro1, socio1);

prestamo1.MostrarFicha(); // Prestamo de 'Rayuela' a Ana Martinez el <fecha de hoy> (en curso)

Con esto, el modelo de dominio de BiblioTech queda formado por tres clases que colaboran entre sí: Libro, Socio y Prestamo. Este trío se mantendrá estable durante el resto del curso, y lo irás enriqueciendo (herencia, encapsulamiento, colecciones, persistencia...) sin cambiar su propósito esencial.

  1. Destructores y una mención a IDisposable

Así como un constructor se ejecuta al crear un objeto, un destructor (también llamado finalizador) es un método especial que el recolector de basura (garbage collector) de .NET ejecuta, en algún momento no determinado, antes de liberar definitivamente la memoria de un objeto que ya no se usa. Se escribe con el símbolo ~ seguido del nombre de la clase, sin modificador de acceso ni parámetros:

class Libro
{
    // ... propiedades y constructores ...

    ~Libro()
    {
        Console.WriteLine($"El objeto Libro '{Titulo}' esta siendo destruido.");
    }
}

En la práctica, los destructores se usan muy poco en C# moderno, por varias razones: no puedes controlar cuándo se ejecutan exactamente (depende del recolector de basura, no de tu código), añaden una sobrecarga de rendimiento, y la mayoría de la limpieza de recursos que un destructor podría hacer (cerrar ficheros, conexiones de red o de base de datos) se gestiona hoy en día con la interfaz IDisposable y la instrucción using, que ofrecen un control determinista y explícito sobre cuándo se libera cada recurso. IDisposable se estudiará junto con el manejo de ficheros y bases de datos en el Módulo 5; de momento basta con que sepas que existe, y que es la alternativa preferida frente a los destructores para liberar recursos de forma fiable.

Constructor Destructor
Símbolo Ninguno (mismo nombre que la clase) ~ antes del nombre de la clase
Cuándo se ejecuta Al crear el objeto con new (inmediato y predecible) En algún momento indeterminado, decidido por el recolector de basura
Frecuencia de uso en C# moderno Muy habitual Rara; se prefiere IDisposable (Módulo 5)

Errores Comunes y Consejos

  • Definir un constructor con parámetros y perder el constructor sin parámetros sin darse cuenta: en cuanto añades cualquier constructor propio, el constructor por defecto desaparece; si sigues necesitando new Libro() sin argumentos, debes declararlo tú mismo.
  • Repetir lógica de inicialización en varios constructores: si dos o más constructores comparten parte de su lógica, usa this(...) para encadenarlos en lugar de copiar y pegar el mismo código en cada uno.
  • Confundir el nombre del parámetro con el de la propiedad: Titulo = titulo; asigna a la propiedad (Titulo, con mayúscula) el valor del parámetro (titulo, con minúscula); si escribieras titulo = titulo; por error, no ocurriría nada útil.
  • Usar DateTime en lugar de DateTime? cuando el valor puede no existir todavía: como FechaDevolucion puede no tener valor mientras el préstamo está en curso, debe ser un tipo nullable (DateTime?); usar DateTime a secas obligaría a inventar una fecha "falsa" para representar "todavía no devuelto".
  • Confiar en el destructor para liberar recursos importantes: como no se puede predecir cuándo se ejecutará, no es una herramienta fiable para cerrar ficheros o conexiones a tiempo; para eso está IDisposable (Módulo 5).

Ejercicios

  1. Define una clase Libro con las propiedades Titulo, Autor, Isbn (todas string) y Disponible (bool, inicializada mediante un inicializador de propiedad a true). Añade un único constructor Libro(string titulo, string autor, string isbn) que asigne las tres propiedades de texto. Crea un objeto y comprueba que Disponible vale true sin haberlo asignado en el constructor.

  2. Añade a la clase anterior un segundo constructor sin parámetros que use this(...) para encadenar con el constructor de tres parámetros, pasando los valores "Titulo desconocido", "Autor desconocido" y "". Crea un objeto con new Libro() y muestra su Titulo para comprobar que el encadenamiento funciona.

  3. Crea la clase Socio con las propiedades Id (int) y Nombre (string) y un constructor Socio(int id, string nombre). Después, crea un objeto Libro (usando el constructor del ejercicio 1) y un objeto Socio, y usa ambos para construir manualmente un objeto Prestamo (según la clase definida en el apartado 8 de esta lección), mostrando su ficha con MostrarFicha().

Soluciones

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;
    }
}

Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(libro1.Disponible); // True
public Libro() : this("Titulo desconocido", "Autor desconocido", "")
{
}

Libro libroVacio = new Libro();
Console.WriteLine(libroVacio.Titulo); // Titulo desconocido

El constructor sin parámetros delega en el de tres parámetros mediante this(...), por lo que Titulo, Autor e Isbn acaban recibiendo los valores por defecto indicados, sin duplicar código.

class Socio
{
    public int Id { get; set; }
    public string Nombre { get; set; }

    public Socio(int id, string nombre)
    {
        Id = id;
        Nombre = nombre;
    }
}

Libro libro1 = new Libro("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1");
Socio socio1 = new Socio(1, "Ana Martinez");

Prestamo prestamo1 = new Prestamo(libro1, socio1);
prestamo1.MostrarFicha(); // Prestamo de 'Ficciones' a Ana Martinez el <fecha de hoy> (en curso)

Conclusión

En esta lección has aprendido a garantizar que los objetos nazcan siempre en un estado coherente mediante constructores: el constructor por defecto, constructores con parámetros, su sobrecarga, el encadenamiento con this(...) y los inicializadores de propiedad; también has visto qué son los destructores y por qué en C# moderno se prefiere IDisposable para liberar recursos. Con esto, el modelo de dominio de BiblioTech queda formado por tres clases que colaboran entre sí: Libro (título, autor, ISBN, disponibilidad), Socio (identificador y nombre) y Prestamo (qué libro, qué socio, cuándo se prestó y cuándo se devolvió).

Este trío de clases, sin embargo, tiene ahora mismo cierta duplicación oculta: si BiblioTech quisiera prestar también revistas, tendría que repetir en una nueva clase Revista las mismas propiedades (Titulo, Autor, Disponible) y métodos (Prestar(), Devolver()) que ya tiene Libro. En la próxima lección, Herencia, aprenderás a evitar esa duplicación extrayendo lo común a una clase base compartida, MaterialBibliotecario, de la que Libro (y la nueva Revista) heredará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