En la lección de Polimorfismo (Módulo 3) ya usaste is Libro libro para comprobar el tipo real de un MaterialBibliotecario en tiempo de ejecución, y en Sentencias Switch (Módulo 2) viste un primer adelanto de las switch expressions. Ambas ideas forman parte de una característica más amplia de C# moderno: el pattern matching, que permite comprobar no solo el tipo de un valor, sino también sus propiedades, sus rangos y combinaciones lógicas entre condiciones, todo en una sintaxis compacta. Esta lección reúne el pattern matching, las switch expressions, los nullable reference types y un recordatorio de los records, como el conjunto de características que definen el estilo del C# actual, y las aplica para clasificar y filtrar materiales de BiblioTech.

Contenido

  1. Recordatorio: patrones de tipo con is
  2. Patrones de propiedad: { Disponible: true }
  3. Patrones relacionales y patrones lógicos: and, or, not, >, <
  4. Switch expressions: la evolución del switch clásico
  5. Patrones en switch expressions: combinando tipo y propiedad
  6. Nullable reference types: #nullable enable, ? y el operador !
  7. Records como parte del C# moderno: recordatorio y patrón posicional

  1. Recordatorio: patrones de tipo con is

En la lección de Polimorfismo ya escribiste código como este, usando un patrón de tipo (type pattern):

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

if (material is Libro libro)
{
    Console.WriteLine($"Es un libro con ISBN {libro.Isbn}");
}

material is Libro libro comprueba, en tiempo de ejecución, si material es (o hereda de) Libro, y si lo es, declara automáticamente una nueva variable libro de tipo Libro, ya convertida, disponible dentro del bloque. Esto ya es pattern matching: un patrón de tipo es el más simple de todos los patrones que ofrece C#, y a partir de aquí la lección presenta patrones más expresivos que se combinan con él.

  1. Patrones de propiedad: { Disponible: true }

Un patrón de propiedad (property pattern) comprueba el valor de una o más propiedades de un objeto, sin necesidad de una variable intermedia:

if (material is { Disponible: true })
{
    Console.WriteLine("El material esta disponible para prestamo.");
}

material is { Disponible: true } se lee como "si material tiene una propiedad Disponible igual a true". Este patrón se puede combinar con un patrón de tipo, comprobando a la vez el tipo concreto y una de sus propiedades:

if (material is Libro { Disponible: true } libro)
{
    Console.WriteLine($"'{libro.Titulo}' es un libro disponible.");
}

Aquí se comprueban, en una sola expresión, tres cosas a la vez: que material sea (en tiempo de ejecución) un Libro, que su Disponible sea true, y además se obtiene la variable libro ya convertida, todo en una línea que antes habría requerido un if anidado.

  1. Patrones relacionales y patrones lógicos: and, or, not, >, <

Los patrones relacionales comparan un valor numérico usando los operadores <, >, <=, >= directamente dentro de un patrón:

Revista revista1 = new Revista("National Geographic", "Varios autores", 302);

if (revista1 is Revista { NumeroEdicion: > 100 })
{
    Console.WriteLine("Revista de larga trayectoria (mas de 100 ediciones).");
}

Los patrones lógicos combinan varios patrones con las palabras clave and, or y not (en lugar de &&, ||, !, reservados para expresiones booleanas normales):

bool esRevistaConsolidada = revista1 is Revista { NumeroEdicion: > 50 and < 1000 };

bool esDisponibleYNoRevista = material is not Revista and { Disponible: true };
Patrón Ejemplo Significado
De tipo material is Libro ¿Es (en tiempo de ejecución) un Libro?
De tipo con variable material is Libro libro Igual que el anterior, y además asigna libro
De propiedad material is { Disponible: true } ¿La propiedad Disponible vale true?
Relacional numeroEdicion is > 100 ¿El valor es mayor que 100?
Lógico and/or/not material is not Revista and { Disponible: true } Combina varios patrones con lógica
Combinado (tipo + propiedad) material is Libro { Disponible: true } libro Tipo, propiedad y variable a la vez

Estos patrones no están limitados a if: se pueden usar en cualquier expresión is, y —lo más habitual en código moderno— dentro de una switch expression, que es el tema del siguiente apartado.

  1. Switch expressions: la evolución del switch clásico

En la lección de Sentencias Switch (Módulo 2) viste un primer adelanto de las switch expressions: una forma de escribir un switch que devuelve un valor directamente, sin case/break repetidos, usando => para cada posible resultado:

string categoria = "Novela";

string seccion = categoria switch
{
    "Novela" => "Narrativa - Planta 1",
    "Ensayo" => "No ficcion - Planta 2",
    "Poesia" => "Poesia - Planta 1",
    _ => "Categoria no reconocida"
};
switch clásico (sentencia) Switch expression
¿Devuelve un valor directamente? No; hay que asignar dentro de cada case Sí; toda la expresión se evalúa a un valor
Palabra clave por caso case valor: ... break; patron => valor,
Caso por defecto default: _ (guion bajo)
Verbosidad Mayor (llaves, break en cada caso) Menor (una línea por caso, en general)
Compatible con patrones (tipo, propiedad, relacional...) Limitado Completo

La diferencia más importante para esta lección es la última fila: mientras que el switch clásico solo compara igualdad exacta contra valores constantes, una switch expression admite cualquier patrón de los vistos en los apartados 1 a 3, lo que la convierte en la herramienta natural para clasificar objetos como los de BiblioTech según su tipo real y sus propiedades.

  1. Patrones en switch expressions: combinando tipo y propiedad

Así se clasifica un MaterialBibliotecario combinando patrones de tipo y de propiedad dentro de una switch expression:

string Clasificar(MaterialBibliotecario material) => material switch
{
    Libro { Disponible: true } => $"Libro disponible: {material.Titulo}",
    Libro => $"Libro prestado: {material.Titulo}",
    Revista { NumeroEdicion: > 100 } => $"Revista de larga trayectoria: {material.Titulo}",
    Revista => $"Revista: {material.Titulo}",
    _ => "Tipo de material no reconocido"
};
Libro libro1 = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Revista revista1 = new Revista("National Geographic", "Varios autores", 302);

libro1.Prestar();

Console.WriteLine(Clasificar(libro1));    // "Libro prestado: Rayuela"
Console.WriteLine(Clasificar(revista1));  // "Revista de larga trayectoria: National Geographic"

El orden de los casos importa, igual que en un switch clásico: C# evalúa cada patrón de arriba abajo y ejecuta el primero que coincida. Por eso Libro { Disponible: true } debe ir antes que Libro a secas: si Libro fuera el primer caso, siempre coincidiría antes de comprobar Disponible, y el caso más específico nunca se alcanzaría.

Filtrar una lista completa del catálogo reutiliza exactamente el mismo patrón, esta vez dentro de una expresión booleana con LINQ (que se estudiará en detalle en una lección posterior de este módulo):

List<MaterialBibliotecario> catalogo = new List<MaterialBibliotecario> { libro1, revista1 };

foreach (MaterialBibliotecario material in catalogo)
{
    if (material is Libro and { Disponible: true })
    {
        Console.WriteLine($"Disponible para prestamo: {material.Titulo}");
    }
}

  1. Nullable reference types: #nullable enable, ? y el operador !

Ya usaste Libro? en la lección de Polimorfismo (Libro? libroCasteado = primerElemento as Libro;) sin detenerte en el porqué del signo ?. C# moderno permite activar los nullable reference types, una característica que hace que el compilador distinga, para los tipos por referencia (class, no solo string/int?), entre "nunca puede ser null" y "puede ser null", avisando con advertencias cuando el código no respeta esa distinción.

Se activa (muchos proyectos nuevos de .NET lo traen activado por defecto) con esta directiva al principio del fichero:

#nullable enable

Con esta directiva activa, un tipo como Libro significa "nunca null" salvo que se declare explícitamente Libro?:

Libro? BuscarLibroPorIsbn(List<Libro> libros, string isbn)
{
    foreach (Libro libro in libros)
    {
        if (libro.Isbn == isbn)
        {
            return libro;
        }
    }

    return null; // valido: el tipo de retorno es Libro?, admite null
}
Libro? resultado = BuscarLibroPorIsbn(catalogoLibros, "978-84-376-0495-4");

if (resultado is not null)
{
    Console.WriteLine(resultado.Titulo); // dentro del "if", el compilador sabe que no es null
}

El operador ! (llamado null-forgiving operator, "operador de indulgencia con null") le dice al compilador "confía en mí, sé que este valor no es null aquí, aunque su tipo sea nullable", silenciando la advertencia sin añadir ninguna comprobación real en tiempo de ejecución:

Libro libroSeguro = BuscarLibroPorIsbn(catalogoLibros, "978-84-376-0495-4")!;
// Advertencia silenciada, pero si el resultado fuera realmente null, esto lanzaria
// NullReferenceException en tiempo de ejecucion al usar libroSeguro mas adelante.
Herramienta Qué expresa
Libro (sin ?) El compilador espera que nunca sea null; avisa si detecta lo contrario
Libro? Puede ser null; el compilador exige comprobarlo antes de usarlo con confianza
is not null / is null Patrón recomendado para comprobar nulidad (más claro que != null)
valor! "Confío en que no es null aquí", sin comprobación real; úsalo con moderación

Es importante entender que los nullable reference types son, sobre todo, una ayuda del compilador en tiempo de compilación (avisos, no errores duros en la mayoría de los casos): no impiden que un null real llegue en tiempo de ejecución si el operador ! se usa de forma incorrecta, pero ayudan enormemente a detectar posibles NullReferenceException antes de ejecutar el programa.

  1. Records como parte del C# moderno: recordatorio y patrón posicional

La última lección del Módulo 3 presentó los record, con ResumenPrestamo como ejemplo:

record ResumenPrestamo(string TituloLibro, string NombreSocio, DateTime FechaPrestamo);

Los records forman parte, junto con el pattern matching, las switch expressions y los nullable reference types, del conjunto de características que definen el C# moderno: todas comparten el objetivo de expresar más significado con menos código, y de detectar más errores en tiempo de compilación. Una conexión directa entre records y pattern matching es el patrón posicional, que deconstruye un record directamente en variables, usando el mismo orden que su constructor:

ResumenPrestamo resumen = new ResumenPrestamo("Rayuela", "Ana Martinez", new DateTime(2026, 1, 10));

if (resumen is ResumenPrestamo(var tituloLibro, var nombreSocio, _))
{
    Console.WriteLine($"{nombreSocio} tiene en prestamo '{tituloLibro}'");
}

ResumenPrestamo(var tituloLibro, var nombreSocio, _) extrae las tres propiedades posicionales del record en dos variables nuevas, descartando la tercera con _ (el mismo guion bajo que ya conoces como caso "por defecto" de una switch expression, aquí usado como "no me interesa este valor"). Esta capacidad de deconstrucción automática es otra ventaja que los records ofrecen "gratis", sin escribir código adicional, gracias a su sintaxis posicional.

Errores Comunes y Consejos

  • Olvidar el orden en una switch expression con patrones de tipo: igual que en un switch clásico con case apilados, un caso más general antes que uno más específico "tapa" al segundo; coloca siempre los patrones más restrictivos (Libro { Disponible: true }) antes que los más generales (Libro).
  • Confundir and/or/not de los patrones con &&/||/! de las expresiones booleanas: son sintaxis distintas para contextos distintos; dentro de un patrón (is ..., o dentro de un case de switch expression) se usan las palabras and/or/not, no los operadores simbólicos.
  • Activar #nullable enable a mitad de proyecto y esperar cero advertencias: al activarlo sobre código ya existente es normal (y esperable) que aparezcan avisos en sitios que necesitan revisión; no ignores esos avisos sistemáticamente, revísalos uno a uno.
  • Abusar del operador !: usarlo para silenciar cualquier aviso sin analizar si el valor puede ser realmente null reintroduce, por la puerta de atrás, el mismo riesgo de NullReferenceException que los nullable reference types están pensados para prevenir.
  • Consejo: prefiere is not null a != null (y is null a == null) en código moderno; además de ser más legible, is no se puede sobrescribir de forma sorprendente como sí puede ocurrir, en tipos concretos, con el operador ==.

Ejercicios

  1. Escribe un método string Clasificar(MaterialBibliotecario material) que use una switch expression con patrones de tipo y de propiedad para devolver: "Libro disponible" si es un Libro con Disponible: true, "Libro prestado" si es un Libro no disponible, "Revista" en cualquier otro caso de Revista, y "Desconocido" para cualquier otro tipo. Pruébalo con un Libro disponible, uno prestado y una Revista.

  2. Activa #nullable enable al principio de un fichero de prueba. Escribe un método Libro? BuscarPorTitulo(List<Libro> libros, string titulo) que devuelva el primer libro cuyo Titulo coincida, o null si no lo encuentra. Llama al método dos veces (un título que exista y otro que no) y, en cada caso, comprueba con is not null antes de acceder a sus propiedades.

  3. Dado un record ResumenPrestamo(string TituloLibro, string NombreSocio, DateTime FechaPrestamo), crea un objeto de ejemplo y usa un patrón posicional (is ResumenPrestamo(var titulo, var nombre, var fecha)) para extraer sus tres valores en variables nuevas y mostrarlos por separado.

Soluciones

string Clasificar(MaterialBibliotecario material) => material switch
{
    Libro { Disponible: true } => "Libro disponible",
    Libro => "Libro prestado",
    Revista => "Revista",
    _ => "Desconocido"
};

Libro libroDisponible = new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4");
Libro libroPrestado = new Libro("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1");
libroPrestado.Prestar();
Revista revista1 = new Revista("National Geographic", "Varios autores", 302);

Console.WriteLine(Clasificar(libroDisponible)); // "Libro disponible"
Console.WriteLine(Clasificar(libroPrestado));   // "Libro prestado"
Console.WriteLine(Clasificar(revista1));        // "Revista"
#nullable enable

Libro? BuscarPorTitulo(List<Libro> libros, string titulo)
{
    foreach (Libro libro in libros)
    {
        if (libro.Titulo == titulo)
        {
            return libro;
        }
    }
    return null;
}

List<Libro> libros = new List<Libro>
{
    new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4")
};

Libro? encontrado = BuscarPorTitulo(libros, "Rayuela");
if (encontrado is not null)
{
    Console.WriteLine(encontrado.Isbn); // Ok, dentro del "if" ya se sabe que no es null
}

Libro? noEncontrado = BuscarPorTitulo(libros, "Otro titulo");
if (noEncontrado is null)
{
    Console.WriteLine("No se encontro el libro.");
}
record ResumenPrestamo(string TituloLibro, string NombreSocio, DateTime FechaPrestamo);

ResumenPrestamo resumen = new ResumenPrestamo("Rayuela", "Ana Martinez", new DateTime(2026, 1, 10));

if (resumen is ResumenPrestamo(var titulo, var nombre, var fecha))
{
    Console.WriteLine($"Titulo: {titulo}");
    Console.WriteLine($"Socio: {nombre}");
    Console.WriteLine($"Fecha: {fecha:yyyy-MM-dd}");
}

Conclusión

En esta lección has ampliado el is y el switch que ya conocías con patrones de tipo, de propiedad, relacionales y lógicos; has aprendido a usar switch expressions con esos mismos patrones para clasificar objetos de forma compacta; has activado los nullable reference types para que el compilador te ayude a prevenir referencias nulas; y has visto los records, ya conocidos del Módulo 3, deconstruirse con un patrón posicional. Todo esto forma, en conjunto, el estilo idiomático del C# actual.

Hasta ahora, cada ejemplo de BiblioTech ha trabajado con objetos individuales o, como mucho, con un List<MaterialBibliotecario> improvisado dentro de un método. La próxima lección da un paso atrás para introducir los genéricos: la característica que hace posible que List<T> funcione con cualquier tipo, y que te permitirá escribir tus propias clases y métodos reutilizables para cualquier tipo de dato, antes de construir, en la lección de Colecciones, la clase Biblioteca completa.

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