CSharp

Tutorial CSharp: Pattern Matching

Aprende pattern matching en C# usando is y switch para escribir código más conciso y legible con ejemplos prácticos y avanzados.

Aprende CSharp y certifícate

Patterns con is y switch

El pattern matching en C# es una técnica que permite examinar un valor y determinar si coincide con cierto patrón, extrayendo información útil cuando hay coincidencia. Vamos a explorar cómo funciona con los operadores is y switch.

Operador is con patrones

El operador is tradicionalmente se usaba solo para comprobar tipos, pero con el pattern matching se ha expandido para permitir verificaciones más sofisticadas y extraer datos en una sola operación.

La sintaxis básica es:

if (expresion is patron) 
{
    // Código que se ejecuta si la expresión coincide con el patrón
}

Veamos algunos ejemplos prácticos:

  • Patrón de tipo con variable: Comprueba el tipo y asigna a una variable en una sola operación.
object valor = "Hola mundo";

// Forma tradicional (sin pattern matching)
if (valor is string)
{
    string texto = (string)valor;
    Console.WriteLine($"El valor es un texto de longitud {texto.Length}");
}

// Con pattern matching
if (valor is string texto)
{
    Console.WriteLine($"El valor es un texto de longitud {texto.Length}");
}
  • Patrón constante: Comprueba si un valor coincide con una constante específica.
int statusCode = 200;

if (statusCode is 200)
{
    Console.WriteLine("Operación exitosa");
}
  • Patrón relacional: Permite comparaciones con operadores relacionales.
int temperatura = 25;

if (temperatura is > 20 and < 30)
{
    Console.WriteLine("Temperatura agradable");
}

Switch con patrones

El switch tradicional en C# solo permitía comparar una variable con constantes. Con pattern matching, la instrucción switch se vuelve mucho más potente:

object item = 5;

switch (item)
{
    case int n when n > 0:
        Console.WriteLine($"Número positivo: {n}");
        break;
    case int n:
        Console.WriteLine($"Número no positivo: {n}");
        break;
    case string s:
        Console.WriteLine($"Texto: {s}");
        break;
    case null:
        Console.WriteLine("Valor nulo");
        break;
    default:
        Console.WriteLine("Otro tipo");
        break;
}

En este ejemplo:

  • case int n when n > 0: Coincide con enteros positivos
  • case int n: Coincide con cualquier otro entero
  • case string s: Coincide con cadenas de texto
  • case null: Coincide con valores nulos

Patrones combinados

Podemos combinar patrones usando operadores lógicos:

if (valor is string s and { Length: > 5 })
{
    Console.WriteLine($"Es un texto con más de 5 caracteres: {s}");
}

if (valor is not null and not int)
{
    Console.WriteLine("No es nulo ni un entero");
}

Ejemplo práctico

Veamos un ejemplo más completo donde el pattern matching simplifica el código para procesar diferentes tipos de datos:

public static string DescribirItem(object item)
{
    return item switch
    {
        null => "Elemento nulo",
        int n when n == 0 => "Número cero",
        int n when n < 0 => $"Número negativo: {n}",
        int n => $"Número positivo: {n}",
        string s when string.IsNullOrEmpty(s) => "Cadena vacía",
        string s => $"Texto: {s} (longitud: {s.Length})",
        DateTime d => $"Fecha: {d.ToShortDateString()}",
        _ => $"Otro tipo: {item.GetType().Name}"
    };
}

Podemos usar esta función así:

Console.WriteLine(DescribirItem(42));        // "Número positivo: 42"
Console.WriteLine(DescribirItem("Hola"));    // "Texto: Hola (longitud: 4)"
Console.WriteLine(DescribirItem(null));      // "Elemento nulo"
Console.WriteLine(DescribirItem(DateTime.Now)); // "Fecha: 01/01/2023" (depende de la fecha actual)

Ventajas del pattern matching con is y switch

  • Código más conciso: Reduce la cantidad de código necesario para realizar comprobaciones y extracciones.
  • Mayor legibilidad: Hace que el código sea más expresivo y fácil de entender.
  • Menos propenso a errores: Evita errores comunes como olvidar verificar tipos antes de convertir.
  • Más flexible: Permite combinar diferentes tipos de patrones para crear condiciones complejas.

El pattern matching con is y switch es especialmente útil cuando trabajamos con jerarquías de clases, tipos de datos variados o cuando necesitamos extraer información basada en la estructura de los datos. Esta característica hace que el código sea más declarativo, centrándose en qué estamos buscando en lugar de cómo lo estamos verificando.

Patrones de propiedad

Los patrones de propiedad son una característica poderosa del pattern matching en C# que nos permite examinar las propiedades de un objeto directamente en la expresión de coincidencia. Esta funcionalidad nos ayuda a escribir código más declarativo y conciso cuando necesitamos verificar características específicas de objetos.

La sintaxis básica de un patrón de propiedad utiliza llaves { } para acceder a las propiedades del objeto:

if (objeto is { Propiedad1: valor1, Propiedad2: valor2 })
{
    // Código que se ejecuta si las propiedades coinciden
}

Patrones de propiedad simples

Veamos un ejemplo sencillo con una clase Producto:

public class Producto
{
    public string Nombre { get; set; }
    public decimal Precio { get; set; }
    public bool EnStock { get; set; }
}

Podemos usar patrones de propiedad para verificar características específicas:

Producto producto = new Producto { Nombre = "Laptop", Precio = 1200, EnStock = true };

// Verificar si es un producto disponible y costoso
if (producto is { EnStock: true, Precio: > 1000 })
{
    Console.WriteLine($"El producto {producto.Nombre} está disponible pero es costoso");
}

// Verificar si es un producto con nombre específico
if (producto is { Nombre: "Laptop" })
{
    Console.WriteLine("Es una laptop");
}

Anidamiento de patrones de propiedad

Una característica muy útil es la capacidad de anidar patrones para examinar propiedades de objetos dentro de otros objetos:

public class Cliente
{
    public string Nombre { get; set; }
    public Direccion DireccionEnvio { get; set; }
}

public class Direccion
{
    public string Ciudad { get; set; }
    public string Pais { get; set; }
    public bool EsInternacional => Pais != "España";
}

Podemos verificar propiedades anidadas de esta manera:

Cliente cliente = new Cliente
{
    Nombre = "Ana",
    DireccionEnvio = new Direccion { Ciudad = "Barcelona", Pais = "España" }
};

// Verificar propiedades anidadas
if (cliente is { DireccionEnvio: { Ciudad: "Barcelona" } })
{
    Console.WriteLine("Cliente con envío a Barcelona");
}

// Combinar con patrones relacionales
if (cliente is { DireccionEnvio: { EsInternacional: false } })
{
    Console.WriteLine("Envío nacional");
}

Combinación con patrones de tipo

Los patrones de propiedad se pueden combinar con patrones de tipo para verificar simultáneamente el tipo y las propiedades:

object item = new Producto { Nombre = "Monitor", Precio = 300, EnStock = true };

if (item is Producto { EnStock: true, Precio: < 500 })
{
    Console.WriteLine("Es un producto económico en stock");
}

También podemos asignar el objeto a una variable durante la verificación:

if (item is Producto p && p.Precio < 500)
{
    // Forma tradicional
}

// Con pattern matching
if (item is Producto { Precio: < 500 } p)
{
    Console.WriteLine($"Producto económico: {p.Nombre}");
}

Uso en instrucciones switch

Los patrones de propiedad son especialmente útiles en instrucciones switch:

public string ClasificarProducto(Producto p)
{
    return p switch
    {
        { EnStock: false } => "No disponible",
        { Precio: < 100 } => "Económico",
        { Precio: >= 100 and < 500 } => "Precio medio",
        { Precio: >= 500, Nombre: "Laptop" } => "Laptop premium",
        { Precio: >= 500 } => "Producto premium",
        _ => "Producto sin clasificar"
    };
}

Patrones de propiedad con descarte

Podemos usar el carácter de descarte _ cuando solo nos interesa verificar algunas propiedades:

if (producto is { Nombre: _, Precio: > 0, EnStock: true })
{
    Console.WriteLine("Producto válido con precio positivo y en stock");
}

Ejemplo práctico: Sistema de notificaciones

Veamos un ejemplo más completo donde los patrones de propiedad simplifican el procesamiento de diferentes tipos de notificaciones:

public abstract class Notificacion
{
    public DateTime FechaCreacion { get; set; }
    public bool Leida { get; set; }
}

public class NotificacionEmail : Notificacion
{
    public string DireccionEmail { get; set; }
    public string Asunto { get; set; }
}

public class NotificacionSMS : Notificacion
{
    public string NumeroTelefono { get; set; }
}

public class NotificacionPush : Notificacion
{
    public string DispositivoID { get; set; }
    public int Prioridad { get; set; }
}

Podemos procesar estas notificaciones de manera elegante:

public string ProcesarNotificacion(Notificacion notificacion)
{
    return notificacion switch
    {
        { Leida: true } => "Notificación ya leída",
        
        NotificacionEmail { DireccionEmail: null } => "Email inválido",
        NotificacionEmail { Asunto: "Urgente" } email => 
            $"Email urgente para: {email.DireccionEmail}",
        NotificacionEmail email => $"Email normal para: {email.DireccionEmail}",
        
        NotificacionSMS { NumeroTelefono: var numero } when numero.StartsWith("+34") => 
            "SMS nacional",
        NotificacionSMS => "SMS internacional",
        
        NotificacionPush { Prioridad: > 5 } => "Notificación push urgente",
        NotificacionPush => "Notificación push normal",
        
        _ => "Notificación desconocida"
    };
}

Este enfoque hace que el código sea más declarativo y fácil de leer, ya que nos centramos en las características que estamos buscando en lugar de escribir múltiples condiciones anidadas.

Los patrones de propiedad son particularmente útiles cuando trabajamos con:

  • Objetos con múltiples propiedades que necesitamos verificar
  • Estructuras de datos anidadas
  • Validaciones complejas que dependen de varias propiedades
  • Clasificación de objetos basada en sus características

Esta característica del pattern matching nos permite escribir código más limpio y expresivo, reduciendo la cantidad de código repetitivo y mejorando la legibilidad.

Switch expressions

Las switch expressions representan una evolución moderna y concisa de las tradicionales instrucciones switch en C#. Introducidas en C# 8.0, estas expresiones permiten evaluar un valor y devolver un resultado basado en patrones de coincidencia, todo en una sintaxis más compacta y expresiva.

A diferencia de la instrucción switch tradicional, una switch expression:

  • Es una expresión que devuelve un valor (no una instrucción)
  • Utiliza la sintaxis => (flecha lambda) en lugar de case y break
  • No requiere la palabra clave default, sino que usa _ como patrón de descarte

Sintaxis básica

La estructura básica de una switch expression es:

resultado = valor switch
{
    patrón1 => expresión1,
    patrón2 => expresión2,
    _ => expresiónPorDefecto
};

Veamos un ejemplo sencillo que convierte un día de la semana en un mensaje:

string mensaje = diaSemana switch
{
    "Lunes" => "Inicio de semana",
    "Viernes" => "¡Casi fin de semana!",
    "Sábado" or "Domingo" => "Fin de semana",
    _ => "Día entre semana"
};

Comparación con switch tradicional

Para apreciar la mejora, comparemos ambos enfoques:

// Switch tradicional
string ObtenerMensajeDia(string dia)
{
    switch (dia)
    {
        case "Lunes":
            return "Inicio de semana";
        case "Viernes":
            return "¡Casi fin de semana!";
        case "Sábado":
        case "Domingo":
            return "Fin de semana";
        default:
            return "Día entre semana";
    }
}

// Switch expression
string ObtenerMensajeDia(string dia) => dia switch
{
    "Lunes" => "Inicio de semana",
    "Viernes" => "¡Casi fin de semana!",
    "Sábado" or "Domingo" => "Fin de semana",
    _ => "Día entre semana"
};

La versión con switch expression es más concisa y elimina la necesidad de palabras clave repetitivas como case, return y break.

Uso con tipos y patrones

Las switch expressions brillan cuando se combinan con pattern matching:

decimal CalcularDescuento(object cliente) => cliente switch
{
    ClientePremium { AñosAntigüedad: > 5 } => 0.20m,
    ClientePremium => 0.15m,
    ClienteRegular { ComprasUltimoAño: > 10 } => 0.10m,
    ClienteRegular => 0.05m,
    _ => 0m
};

Este código evalúa el tipo de cliente y sus propiedades para determinar el descuento aplicable, todo en una expresión compacta.

Patrones posicionales

Las switch expressions también funcionan con patrones posicionales, que son útiles cuando trabajamos con tuplas o tipos con deconstrucción:

string DescribirPunto((int X, int Y) punto) => punto switch
{
    (0, 0) => "Origen",
    (var x, 0) => $"Eje X, posición {x}",
    (0, var y) => $"Eje Y, posición {y}",
    (var x, var y) when x == y => $"Diagonal principal ({x},{y})",
    (var x, var y) => $"Punto en ({x},{y})"
};

Podemos usar esta función así:

Console.WriteLine(DescribirPunto((0, 0)));      // "Origen"
Console.WriteLine(DescribirPunto((5, 0)));      // "Eje X, posición 5"
Console.WriteLine(DescribirPunto((3, 3)));      // "Diagonal principal (3,3)"
Console.WriteLine(DescribirPunto((2, 7)));      // "Punto en (2,7)"

Patrones con guardas

Podemos añadir condiciones adicionales usando la palabra clave when:

string ClasificarTemperatura(int temperatura) => temperatura switch
{
    < 0 => "Bajo cero",
    >= 0 and < 15 => "Frío",
    >= 15 and < 25 => "Templado",
    >= 25 and < 35 => "Caluroso",
    >= 35 => "Muy caluroso"
};

Ejemplo práctico: Procesador de formas geométricas

Veamos un ejemplo más completo donde las switch expressions simplifican el procesamiento de diferentes formas geométricas:

public abstract class Forma { }

public class Círculo : Forma
{
    public double Radio { get; set; }
}

public class Rectángulo : Forma
{
    public double Ancho { get; set; }
    public double Alto { get; set; }
}

public class Triángulo : Forma
{
    public double Base { get; set; }
    public double Altura { get; set; }
}

public static double CalcularÁrea(Forma forma) => forma switch
{
    Círculo c => Math.PI * c.Radio * c.Radio,
    Rectángulo r => r.Ancho * r.Alto,
    Triángulo t => (t.Base * t.Altura) / 2,
    _ => throw new ArgumentException("Forma desconocida")
};

Podemos usar esta función para calcular áreas de diferentes formas:

var formas = new Forma[]
{
    new Círculo { Radio = 5 },
    new Rectángulo { Ancho = 10, Alto = 5 },
    new Triángulo { Base = 8, Altura = 6 }
};

foreach (var forma in formas)
{
    Console.WriteLine($"Área: {CalcularÁrea(forma)}");
}

Uso con enumeraciones

Las switch expressions son particularmente útiles con enumeraciones:

public enum EstadoPedido
{
    Recibido,
    EnProceso,
    Enviado,
    Entregado,
    Cancelado
}

public string ObtenerDescripciónEstado(EstadoPedido estado) => estado switch
{
    EstadoPedido.Recibido => "Pedido recibido, pendiente de procesar",
    EstadoPedido.EnProceso => "Pedido en preparación",
    EstadoPedido.Enviado => "Pedido en camino",
    EstadoPedido.Entregado => "Pedido entregado correctamente",
    EstadoPedido.Cancelado => "Pedido cancelado",
    _ => "Estado desconocido"
};

Beneficios de las switch expressions

  • Concisión: Reducen significativamente la cantidad de código necesario.
  • Expresividad: Hacen que el código sea más declarativo y fácil de leer.
  • Seguridad: Al ser expresiones, el compilador verifica que todos los casos posibles estén cubiertos.
  • Composición: Al devolver valores, pueden usarse dentro de otras expresiones o asignaciones.

Las switch expressions son especialmente útiles cuando necesitamos transformar o clasificar datos basados en patrones, tipos o propiedades. Esta característica moderna de C# nos permite escribir código más limpio y mantenible, eliminando la verbosidad de las estructuras switch tradicionales mientras aprovechamos toda la potencia del pattern matching.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende CSharp online

Ejercicios de esta lección Pattern Matching

Evalúa tus conocimientos de esta lección Pattern Matching con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de CSharp

Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A C#

Introducción Y Entorno

Creación De Proyecto C#

Introducción Y Entorno

Variables Y Constantes

Sintaxis

Tipos De Datos

Sintaxis

Operadores

Sintaxis

Control De Flujo

Sintaxis

Funciones

Sintaxis

Estructuras De Control Iterativo

Sintaxis

Interpolación De Strings

Sintaxis

Estructuras De Control Condicional

Sintaxis

Manejo De Valores Nulos

Sintaxis

Clases Y Encapsulación

Programación Orientada A Objetos

Objetos

Programación Orientada A Objetos

Constructores Y Destructores

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

Genéricos

Programación Orientada A Objetos

Métodos Virtuales Y Sobrecarga

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Propiedades Y Encapsulación

Programación Orientada A Objetos

Métodos De Extensión

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Clases Parciales

Programación Orientada A Objetos

Miembros Estáticos

Programación Orientada A Objetos

Tuplas Y Tipos Anónimos

Programación Orientada A Objetos

Arrays Y Listas

Colecciones Y Linq

Diccionarios

Colecciones Y Linq

Conjuntos, Colas Y Pilas

Colecciones Y Linq

Uso De Consultas Linq

Colecciones Y Linq

Linq Avanzado

Colecciones Y Linq

Colas Y Pilas

Colecciones Y Linq

Conjuntos

Colecciones Y Linq

Linq Básico

Colecciones Y Linq

Delegados Funcionales

Programación Funcional

Records

Programación Funcional

Expresiones Lambda

Programación Funcional

Linq Funcional

Programación Funcional

Fundamentos De La Programación Funcional

Programación Funcional

Pattern Matching

Programación Funcional

Testing Unitario Con Xunit

Testing

Excepciones

Excepciones

Delegados

Programación Asíncrona

Eventos

Programación Asíncrona

Lambdas

Programación Asíncrona

Uso De Async Y Await

Programación Asíncrona

Tareas

Programación Asíncrona

Accede GRATIS a CSharp y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender el uso del operador is con patrones para verificar tipos y extraer datos.
  • Aplicar pattern matching en instrucciones switch tradicionales y switch expressions.
  • Utilizar patrones de propiedad para examinar y combinar propiedades de objetos.
  • Implementar patrones combinados y posicionales para condiciones complejas.
  • Valorar las ventajas del pattern matching para escribir código más conciso, legible y seguro.