Coincidencia de patrones en C#

La coincidencia de patrones es una característica que permite probar una expresión para la ocurrencia de un patrón dado. Es una característica más frecuente en los lenguajes funcionales . La coincidencia de patrones es de naturaleza booleana, lo que implica que hay dos resultados posibles: la expresión coincide con el patrón o no. Esta característica se introdujo por primera vez en C# 7.0 y luego ha sufrido una serie de mejoras en las sucesivas versiones del lenguaje. 

La coincidencia de patrones permite operaciones como:

  • Comprobación de tipo (patrón de tipo)
  • verificación nula (patrón constante)
  • comparaciones (patrón relacional)
  • Comprobación y comparación de valores de propiedades (patrón de propiedad)
  • deconstrucción de objetos (patrón posicional),
  • reutilización de expresiones usando la creación de variables ( patrón var )

expresarse utilizando una sintaxis mínima y sucinta. Además, estos patrones se pueden anidar y pueden comprender varios subpatrones. Los patrones también se pueden combinar usando combinadores de patrones ( y , o y no ). C# permite la coincidencia de patrones a través de tres construcciones:

1. es operador 

Antes de C# 7.0, el único propósito del operador is era verificar si un objeto es compatible con un tipo específico. Desde C# 7.0, el operador is se ha ampliado para probar si una expresión coincide con un patrón.

Sintaxis:

la expresión es patrón

2. cambiar declaraciones

Al igual que se puede usar una declaración de cambio para ejecutar una rama de código ( caso ) probando una expresión para un conjunto de valores, también se puede usar para ejecutar una rama de código probando una expresión para la ocurrencia de un conjunto de valores. patrones.

Sintaxis:

interruptor (expresión)

{

    caso patrón1:

    // código a ejecutar

    // si la expresión coincide con el patrón1

    descanso;

    caso patrón2:

    // código a ejecutar

    // si la expresión coincide con el patrón2

    descanso;

    …

    patrón de casoN:

    // código a ejecutar

    // si la expresión coincide con patrónN

    descanso;

    defecto:

    // código a ejecutar si expresión

    // no coincide con ninguno de los patrones anteriores  

}

3. cambiar expresiones 

También se puede probar un conjunto de patrones mediante una expresión de cambio para seleccionar un valor en función de si el patrón coincide.

Sintaxis:

cambio de expresión

{

    patrón1 => valor1,

    patrón2 => valor2,

    …

    patrónN => valorN,

    _ => valor predeterminado

}

Patrones compatibles con C#

A partir de C# 9, el lenguaje admite los siguientes patrones. 

  • Tipo Patrón
  • Patrón relacional
  • Patrón de propiedad
  • Patrón posicional
  • patrón variable
  • Patrón constante

C# también admite el uso de las siguientes construcciones con coincidencia de patrones:

  • Declaraciones de variables
  • Combinadores de patrones ( y , o y no )
  • Descartar variables ( _ )
  • Patrones anidados o subpatrones

El patrón tipográfico

El patrón de tipo se puede utilizar para comprobar si el tipo de tiempo de ejecución de una expresión coincide con el tipo especificado o es compatible con ese tipo. Si la expresión, es decir, el valor que se compara es compatible con el tipo especificado en el patrón, la coincidencia se realiza correctamente. El patrón de tipo también puede contener opcionalmente una declaración de variable. Si el valor que se está probando coincide con el tipo, se convertirá a este tipo y luego se asignará a esta variable. Las declaraciones de variables en patrones se describen más adelante.

Sintaxis:

// Usado en C# 9 y superior

Escribe un nombre 

// Usado en C# 7

Variable de nombre de tipo

Escribe un nombre _

Ejemplo:

C#

// C# program to illustrate the concept of Type Pattern
using System;
 
public class GFG{
     
static void PrintUppercaseIfString(object arg)
{
    // If arg is a string:
    // convert it to a string
    // and assign it to variable message
    if (arg is string message)
    {
        Console.WriteLine($"{message.ToUpper()}");
    }
    else
    {
        Console.WriteLine($"{arg} is not a string");
    }
}
 
// Driver code
static public void Main()
{
    string str = "Geeks For Geeks";
    int number = 42;
    object o1 = str;
    object o2 = number;
 
    PrintUppercaseIfString(o1);
    PrintUppercaseIfString(o2);
}
}
Producción

GEEKS FOR GEEKS
42 is not a string

En el ejemplo anterior, el método PrintUppercaseIfString() acepta un argumento de tipo objeto llamado arg . Cualquier tipo en C# se puede convertir en objeto porque, en C#, todos los tipos se derivan del objeto. Esto se llama unificación de tipos .

Fundición automática

Si arg es una string , se convertirá de objeto a string y se asignará a una variable llamada mensaje . Si arg no es una string sino un tipo diferente, se ejecutará el bloque else . Por lo tanto, tanto la verificación de tipos como la conversión se combinan en una expresión. Si el tipo no coincide, la variable no se creará. 

Activar tipos con el patrón de tipos

El patrón de tipo utilizado con una declaración de cambio puede ayudar a seleccionar una rama de código ( rama de caso ) según el tipo de valor. El siguiente código define un método llamado PrintType() que acepta un argumento como un objeto y luego imprime diferentes mensajes para diferentes tipos de argumentos:

C#

// C# program to illustrate the concept of Type Pattern Switch
using static System.Console;
 
// Allows using WriteLine without Console. prefix
public class Person
{
    public string Name
    {
        get;
        set;
    }
}
 
class GFG{
     
static void PrintType(object obj)
{
    switch (obj)
    {
      case Person p:
          WriteLine("obj is a Person");
          WriteLine($"Name of the person: {p.Name}");
          break;
 
      case int i:
          WriteLine("obj is an int");
          WriteLine($"Value of the int: {i}");
          break;
 
      case double d:
          WriteLine("obj is a double");
          WriteLine($"Value of the double: {d}");
          break;
 
      default:
          WriteLine("obj is some other type");
          break;
    }
 
    WriteLine(); // New line
}
 
// Driver code
static public void Main()
{
    var person = new Person { Name = "Geek" };
 
    PrintType(42);
    PrintType(person);
    PrintType(3.14);
    PrintType("Hello");
}
}

Producción:

obj is an int
Value of the int: 42

obj is a Person
Name of the person: Geek

obj is a double
Value of the double: 3.14

obj is some other type

Patrones relacionales

Los patrones relacionales se introdujeron en C# 9. Nos ayudan a realizar comparaciones en un valor mediante: <(menor que), <=(menor que o igual a), >(mayor que) y >=(mayor que o igual a ) operadores.

Sintaxis:

< constante

<= constante

> constante

>= constante

Ejemplo:

C#

// Program to check if a number is positive,
// negative or zero using relational patterns
// using a switch statement
using System;
 
class GFG{
     
public static string GetNumberSign(int number)
{
    switch (number)
    {
        case < 0:
            return "Negative";
        case 0:
            return "Zero";
        case >= 1:
            return "Positive";
    }
}
 
// Driver code
static public void Main()
{
    int n1 = 0;
    int n2 = -31;
    int n3 = 18;
     
    Console.WriteLine(GetNumberSign(n1));
    Console.WriteLine(GetNumberSign(n2));
    Console.WriteLine(GetNumberSign(n3));
}
}

Producción:

Zero
Negative
Positive

El ejemplo anterior se puede escribir de manera más concisa usando una expresión de cambio :

C#

// Program to check if a number
// is positive, negative or zero
// using relational patterns
// with a switch expression
using System;
 
class GFG{
     
public static string GetNumberSign(int number)
{
    return number switch
    {
        < 0 => "Negative",
        0 => "Zero",
        >= -1 => "Positive"
    };
}
 
// Driver code
static public void Main()
{
    int n1 = 0;
    int n2 = -31;
    int n3 = 18;
     
    Console.WriteLine(GetNumberSign(n1));
    Console.WriteLine(GetNumberSign(n2));
    Console.WriteLine(GetNumberSign(n3));
}
}

Producción:

Zero
Negative
Positive

De manera similar, los patrones relacionales también se pueden usar con el operador is :

int n = 2;
Console.WriteLine(n is <= 10); // Prints true
Console.WriteLine(n is > 5); // Prints false

Esto puede no ser tan útil por sí solo porque n es <= 10 es lo mismo que escribir n <= 10 . Sin embargo, esta sintaxis será más conveniente con los combinadores de patrones (discutidos más adelante).  
 

Patrones de propiedad

Los patrones de propiedad permiten hacer coincidir los valores de las propiedades definidas en un objeto. El patrón especifica el nombre de la propiedad que debe coincidir y luego, después de dos puntos (:), el valor que debe coincidir. Se pueden especificar múltiples propiedades y sus valores separándolos con comas.

Sintaxis:

{ Propiedad1: valor1, Propiedad2 : valor2, …, PropiedadN: valorN }

Tal sintaxis nos permite escribir:

«Geeks» es { Duración: 4 }

En vez de:

“Geeks”. Longitud == 4

Ejemplo:

C#

// C# program to illustrate the concept of Property Pattern
using System;
 
class GFG{
 
public static void DescribeStringLength(string str)
{
     
    // Constant pattern, discussed further
    if (str is null)
    {
        Console.WriteLine("Null string");
    }
 
    if (str is { Length: 0 })
    {
        Console.WriteLine("Empty string");
        return;
    }
 
    if (str is { Length: 1 })
    {
        Console.WriteLine("String of length 1");
        return;
    }
 
    Console.WriteLine("Length greater than 1");
    return;
}
 
// Driver code
static public void Main()
{
    DescribeStringLength("Hello!");
    Console.WriteLine();
    DescribeStringLength("");
    Console.WriteLine();
    DescribeStringLength("X");
    Console.WriteLine();
}
}

Producción:

Length greater than 1
Empty string
String of length 1

Patrones posicionales

Los patrones posicionales permiten especificar un conjunto de valores entre paréntesis y coincidirán si cada valor entre paréntesis coincide con los valores del objeto coincidente. Los valores de los objetos se extraen a través de la deconstrucción . Los patrones posicionales se basan en el patrón de deconstrucción. Los siguientes tipos pueden usar patrones posicionales:

Sintaxis:

(constante1, constante2, …)

Ejemplo 1: patrón posicional con un tipo que define un método Deconstruct()

El siguiente código define dos funciones LogicalAnd() y LogicalOr() , las cuales aceptan un objeto de BooleanInput. BooleanInput es un tipo de valor (estructura) que representa dos valores de entrada booleanos. Los métodos usan estos dos valores de entrada y realizan una operación Lógica AND y Lógica OR en estos valores. C# ya tiene operadores lógicos AND(&&) y lógicos OR(||) que realizan estas operaciones por nosotros. Sin embargo, los métodos de este ejemplo implementan estas operaciones manualmente para demostrar patrones posicionales.

C#

// C# program to illustrate the concept of Positional Pattern
using System;
 
// Represents two inputs to the truth table
public struct BooleanInput
{
    public bool Input1
    {
        get;
        set;
    }
 
    public bool Input2
    {
        get;
        set;
    }
 
    public void Deconstruct(out bool input1,
                            out bool input2)
    {
        input1 = Input1;
        input2 = Input2;
    }
}
 
class GFG{
     
// Performs logical AND on an input object
public static bool LogicalAnd(BooleanInput input)
{
     
    // Using switch expression
    return input switch
    {
        (false, false) => false,
        (true, false) => false,
        (false, true) => false,
        (true, true) => true
    };
}
 
// Performs logical OR on an input object
public static bool LogicalOr(BooleanInput input)
{
     
    // Using switch statement
    switch (input)
    {
        case (false, false):
            return false;
        case (true, false):
            return true;
        case (false, true):
            return true;
        case (true, true):
            return true;
    }
}
 
// Driver code
static public void Main()
{
    var a = new BooleanInput{Input1 = true,
                             Input2 = false};
    var b = new BooleanInput{Input1 = true,
                             Input2 = true};
   
    Console.WriteLine("Logical AND:");
    Console.WriteLine(LogicalAnd(a));
    Console.WriteLine(LogicalAnd(b));
   
    Console.WriteLine("Logical OR:");
    Console.WriteLine(LogicalOr(a));
    Console.WriteLine(LogicalOr(b));
}
}

 
 Producción: 

Logical AND:
False
True
Logical OR:
True
True

Ejemplo 2: uso de patrones posicionales con tuplas

Cualquier instancia de System.ValueTuple se puede usar en patrones posicionales. C# proporciona una sintaxis abreviada para crear tuplas usando paréntesis:(). Una tupla se puede crear rápidamente sobre la marcha envolviendo un conjunto de variables ya declaradas entre paréntesis. En el siguiente ejemplo, el método LocatePoint() acepta dos parámetros que representan las coordenadas x e y de un punto y luego crea una tupla después de la palabra clave switch usando un par de paréntesis adicional. Los paréntesis exteriores son parte de la sintaxis de la instrucción switch , los paréntesis interiores crean la tupla usando las variables x e y .

C#

// C# program to illustrate the concept of Positional Pattern
using System;
 
class GFG{
     
// Displays the location of a point
// by accepting its x and y coordinates
public static void LocatePoint(int x, int y)
{
    Console.WriteLine($"Point ({x}, {y}):");
     
    // Using switch statement
    // Note the double parantheses
    switch ((x, y))
    {
        case (0, 0):
            Console.WriteLine("Point at origin");
            break;
        case (0, _): // _ will match all values for y
            Console.WriteLine("Point on Y axis");
            break;
        case (_, 0):
            Console.WriteLine("Point on X axis");
            break;
        default:
            Console.WriteLine("Point elsewhere");
            break;
    }
}
 
// Driver code
static public void Main()
{
    LocatePoint(10, 20);
    LocatePoint(10, 0);
    LocatePoint(0, 20);
    LocatePoint(0, 0);
}
}

 
 Producción: 

Point (10, 20):
Point elsewhere
Point (10, 0):
Point on X axis
Point (0, 20):
Point on Y axis
Point (0, 0):
Point at origin

Patrón constante

El patrón constante es la forma más simple de un patrón. Consiste en un valor constante. Se comprueba si la expresión que se está emparejando es igual a esta constante. La constante puede ser:

El patrón constante generalmente aparece como parte de otros patrones como un subpatrón (discutido más adelante), pero también se puede usar solo.

Algunos ejemplos del patrón constante que se usa con el operador is serían:  

expression is 2 // int literal
expression is "Geeks" // string literal
expression is System.DayOfWeek.Monday // enum
expression is null  // null

Observe el ejemplo final, donde el patrón es nulo . Esto implica que la coincidencia de patrones proporciona otra forma de verificar si un objeto es nulo. Además, expression is null puede ser más legible e intuitivo que la expresión típica == null .

En el contexto de una declaración de cambio , el patrón constante parece idéntico a una declaración de cambio normal sin coincidencia de patrón.

Ejemplo: El siguiente ejemplo usa una expresión de cambio con el patrón constante en un método llamado DayOfTheWeek() que devuelve el nombre del día de la semana a partir del número que se le pasa.
 

C#

// C# program to illustrate the concept of Constant Pattern
using System;
 
class GFG{   
 
// Returns the name of the day of the week
public static string DayOfTheWeek(int day)
{
     
    // Switch expression
    return day switch
    {
        1 => "Sunday",
        2 => "Monday",
        3 => "Tuesday",
        4 => "Wednesday",
        5 => "Thursday",
        6 => "Friday",
        7 => "Saturday",
        _ => "Invalid week day"
    };
}
 
// Driver code
static public void Main()
{
    Console.WriteLine(DayOfTheWeek(5));
    Console.WriteLine(DayOfTheWeek(3));
}
}

Producción:

Thursday
Tuesday

El patrón var

El patrón var funciona ligeramente diferente de otros patrones. Una coincidencia de patrón var siempre tiene éxito, lo que significa que el resultado de la coincidencia siempre es verdadero. El propósito del patrón var no es probar una expresión para un patrón, sino asignar una expresión a una variable. Esto permite reutilizar la variable en expresiones consecutivas. El patrón var es una forma más general del patrón de tipo. Sin embargo, no se especifica ningún tipo; en su lugar, se usa var , por lo que no hay verificación de tipos y la coincidencia siempre es exitosa.

Sintaxis:
 

var varNombre

var (varNombre1, varNombre2, …)

Considere el siguiente código donde se debe comparar el día y el mes de un objeto DateTime :

var now = DateTime.Now;
if (now.Month > 6 && now.Day > 15)
{
    // Do Something
}

Esto se puede escribir en una línea usando el patrón var :

if (DateTime.Now is var now && now.Month > 6 && now.Day > 15)
{
    // Do Something
}

Combinadores de patrones / Patrones lógicos

C# 9 también ha introducido combinadores de patrones. Los combinadores de patrones permiten combinar varios patrones juntos. Los siguientes son los combinadores de patrones: 

  • Patrón negativo: no
  • Patrón Conjuntivo: y
  • Patrón disyuntivo o
combinador Palabra clave Descripción Ejemplo
patrón negativo no Invierte una coincidencia de patrón

no 2

no < 10

no nulo

Patrón Conjuntivo y Coincide si ambos patrones coinciden

> 0 y < 10

{ Año: 2002 } y { Mes: 1 }

no int y no doble

patrón disyuntivo o Coincide si al menos uno de los patrones coincide

«Hola» o «Hola» o «Hola»

nulo o (0, 0)

{ Año: 2004 } o { Año: 2002 }

Los combinadores de patrones se parecen mucho a los operadores lógicos (!, &&, ||) pero los operandos son patrones en lugar de condiciones o expresiones booleanas. Los combinadores hacen que la coincidencia de patrones sea más flexible y también ayuda a ahorrar algunas pulsaciones de teclas.

Comparaciones más simples

Mediante el uso de combinadores de patrones con el patrón relacional, una expresión se puede comparar con muchos otros valores sin repetir la expresión una y otra vez. Por ejemplo, considere lo siguiente:

int number = 42;
if (number > 10 && number < 50 && number != 35) 
{
    // Do Something
}

Con la coincidencia de patrones y los combinadores, esto se puede simplificar:

int number = 42;
if (number is > 10 and < 50 and not 35)
{
   // Do Something
}

Como se puede observar, no es necesario repetir el número del nombre de la variable ; las comparaciones se pueden enstringr sin problemas.

Comprobar si un valor no es nulo

En la sección de patrones constantes anterior, se discutió una forma alternativa de verificar si un valor es nulo. Los combinadores de patrones proporcionan una contraparte que permite verificar si un valor no es nulo:

if (expression is not null) 
{
}

Ejemplo: El siguiente ejemplo define un método llamado I sVowel() que verifica si un carácter es una vocal o no usando el combinador de patrones o para combinar múltiples patrones constantes:

C#

// C# program to illustrate the concept of Pattern Combinators
using System;
 
class GFG{
     
public static bool IsVowel(char c)
{
    return char.ToLower(c) is 'a' or 'e' or 'i' or 'o' or 'u';
}
 
// Driver code
public static void Main()
{
    Console.WriteLine(IsVowel('A'));
    Console.WriteLine(IsVowel('B'));
    Console.WriteLine(IsVowel('e'));
    Console.WriteLine(IsVowel('x'));
    Console.WriteLine(IsVowel('O'));
    Console.WriteLine(IsVowel('P'));
}
}

Producción:

True
False
True
False
True
False

Declaraciones de variables

Algunos patrones admiten declarar una variable después del patrón. 

Patrón de tipo: las declaraciones de variables en los patrones de tipo son una forma conveniente de combinar una verificación de tipo y una conversión en un solo paso.

Considera lo siguiente:

object o = 42;
if (o is int)
{
    int i = (int) o;
    //...
}

Esto se puede reducir a un solo paso usando una declaración de variable:

object o = 42;
if (o is int i)
{
    //...
}

Patrones posicionales y de propiedades: los patrones posicionales y de propiedades también permiten una declaración de variables después del patrón:

if (DateTime.Now is { Month: 12 } now)
{
    // Do something with now
}
var p = (10, 20);
if (p is (10, 20) coords)
{
   // Do something with coords
}

Aquí, p y coords contienen el mismo valor y las coords pueden ser de poca utilidad. Pero la sintaxis anterior es legal y, a veces, puede ser útil con un objeto que define un método  Deconstruct() .

Nota: Las declaraciones de variables no están permitidas cuando se usan los combinadores de patrones o y no , pero sí con y .

Descartes Variables

A veces, el valor asignado a las variables durante la coincidencia de patrones puede no ser útil. Los descartes de variables permiten ignorar los valores de tales variables. Un descarte se representa mediante un guión bajo ( _ ).

En patrones de tipos: cuando se usan patrones de tipos con declaraciones de variables como en el siguiente ejemplo:

switch (expression)
{
    case int i:
        Console.WriteLine("i is an integer");
        break;
    ...
}

la variable i nunca se usa. Por lo tanto, se puede descartar:

case int _: 

A partir de C# 9, es posible usar un patrón de tipo sin una variable, lo que también permite eliminar el guión bajo:

case int:

En patrones posicionales: cuando se usan patrones posicionales, se puede usar un descarte como carácter comodín para hacer coincidir todos los valores en ciertas posiciones. Por ejemplo, en el ejemplo de punto anterior, si queremos hacer coincidir cuando la coordenada x es 0 y la coordenada y no importa, lo que significa que el patrón debe coincidir sin importar cuál sea la coordenada y, lo siguiente puede ser hecho:

point is (0, _) // will match for all values of y

Patrones múltiples sin combinadores 

Es posible usar un patrón de tipo, un patrón posicional y un patrón de propiedad juntos sin combinadores.

Sintaxis:

tipo-patrón patrón-posicional patrón-propiedad nombre-variable

Considere la estructura Point :

struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public string Name { get; set; }
    public void Deconstruct(out int x, out int y)
    {
        x = X;
        y = Y;    
    }
}
...
object o = Point() { X = 10, Y = 20, Name = "A" };

En vez de:

if (o is Point p and (10, _) and { Y: 20}) {..}

Se puede escribir lo siguiente:

if (o is Point (10, _) { Y: 20 } p) {..}

Se pueden omitir uno o más de los patrones. Sin embargo, debe haber al menos un patrón, y el orden cuando se usan varios patrones juntos debe ser el mismo que el anterior. Por ejemplo, lo siguiente es ilegal:

if (o is (10, _) Point { Y: 20 } p)

Patrones/subpatrones anidados

Un patrón puede constar de varios subpatrones. Además de la capacidad de combinar múltiples patrones con combinadores de patrones, C# también permite que un solo patrón se componga de varios patrones internos.  

Ejemplos:

Considere la estructura Point anterior y el siguiente punto de objeto :

Point point = new Point() { X = 10, Y = 20, Name = "B" };

Type Pattern y var Pattern en un patrón de propiedad

if (point is { X: int x, Y: var y }) { .. }

Escriba el patrón y el patrón var en un patrón posicional

if (point is (int x, var y)) { .. }

Patrón relacional en un patrón posicional y un patrón de propiedad

switch (point) 
{
    case (< 10, <= 15): 
         .. 
         break;
    case { X: < 10, Y: <= 15 }:
         ..
         break;
    ..
}

Para el siguiente ejemplo, considere los siguientes objetos:

var p1 = new Point { X = 0, Y = 1, Name = "X" };
var p2 = new Point { X = 0, Y = 2, Name = "Y" };

Patrón de propiedades en un patrón posicional

if ((p1, p2) is ({ X: 0 }, { X: 0 })) { .. }

Publicación traducida automáticamente

Artículo escrito por amallkrishna y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *