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ícatePatterns 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 positivoscase int n
: Coincide con cualquier otro enterocase string s
: Coincide con cadenas de textocase 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 decase
ybreak
- 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.
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.
CRUD en C# de modelo Customer sobre una lista
Arrays y listas
Objetos
Excepciones
Eventos
Lambdas
Diccionarios en C#
Variables y constantes
Tipos de datos
Herencia
Operadores
Uso de consultas LINQ
Clases y encapsulación
Uso de consultas LINQ
Excepciones
Control de flujo
Eventos
Diccionarios
Tipos de datos
Conjuntos, colas y pilas
Lambdas
Conjuntos, colas y pilas
Uso de async y await
Tareas
Constructores y destructores
Operadores
Arrays y listas
Polimorfismo
Polimorfismo
Variables y constantes
Proyecto colecciones y LINQ en C#
Clases y encapsulación
Creación de proyecto C#
Uso de async y await
Funciones
Delegados
Delegados
Constructores y destructores
Objetos
Control de flujo
Funciones
Tareas
Proyecto sintaxis en C#
Herencia C Sharp
OOP en C Sharp
Diccionarios
Introducción a C#
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
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.