Patrones (referencia de C#)

C# presentó la coincidencia de patrones en C# 7.0. Desde entonces, cada versión principal de C# extiende las capacidades de coincidencia de patrones. Las siguientes expresiones e instrucciones de C# admiten la coincidencia de patrones:

En esas construcciones, puede hacer coincidir una expresión de entrada con cualquiera de los siguientes patrones:

  • Patrón de declaración: para comprobar el tipo en tiempo de ejecución de una expresión y, si una coincidencia se realiza correctamente, asignar el resultado de una expresión a una variable declarada. Introducido en C# 7.0.
  • Patrón de tipo: para comprobar el tipo en tiempo de ejecución de una expresión. Introducido en C# 9.0.
  • Patrón de constante: para probar si el resultado de una expresión es igual a una constante especificada. Introducido en C# 7.0.
  • Patrones relacionales: para comparar el resultado de una expresión con una constante especificada. Introducido en C# 9.0.
  • Patrones lógicos: para probar si una expresión coincide con una combinación lógica de patrones. Introducido en C# 9.0.
  • Patrón de propiedad: para probar si las propiedades o los campos de una expresión coinciden con los patrones anidados. Introducido en C# 8.0.
  • Patrón posicional: para deconstruir el resultado de una expresión y probar si los valores resultantes coinciden con los patrones anidados. Introducido en C# 8.0.
  • Patrón var : para buscar coincidencias con cualquier expresión y asignar su resultado a una variable declarada. Introducido en C# 7.0.
  • Patrón de descarte: para buscar coincidencias con cualquier expresión. Introducido en C# 8.0.

Los patrones lógicos, de propiedad y posicionales son patrones recursivos. Es decir, pueden contener patrones anidados.

Para obtener el ejemplo de cómo usar esos patrones para compilar un algoritmo basado en datos, vea Tutorial: Uso de la coincidencia de patrones para compilar algoritmos basados en tipos y basados en datos.

Patrones de declaración y de tipo

Los patrones de declaración y de tipo se usan para comprobar si el tipo en tiempo de ejecución de una expresión es compatible con un tipo determinado. Con un patrón de declaración, también puede declarar una nueva variable local. Cuando un patrón de declaración coincide con una expresión, a esa variable se le asigna el resultado de una expresión convertida, como se muestra en el ejemplo siguiente:

object greeting = "Hello, World!";
if (greeting is string message)
{
    Console.WriteLine(message.ToLower());  // output: hello, world!
}

A partir de C# 7.0, un patrón de declaración con el tipo T coincide con una expresión cuando el resultado de una expresión no es NULL y se cumple cualquiera de las condiciones siguientes:

  • El tipo en tiempo de ejecución del resultado de una expresión es T.

  • El tipo en tiempo de ejecución del resultado de una expresión deriva del tipo T, implementa una interfaz T, o bien otra conversión de referencia implícita existe en T. En el ejemplo siguiente se muestran dos casos en los que esta condición es verdadera:

    var numbers = new int[] { 10, 20, 30 };
    Console.WriteLine(GetSourceLabel(numbers));  // output: 1
    
    var letters = new List<char> { 'a', 'b', 'c', 'd' };
    Console.WriteLine(GetSourceLabel(letters));  // output: 2
    
    static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
    {
        Array array => 1,
        ICollection<T> collection => 2,
        _ => 3,
    };
    

    En el ejemplo anterior, en la primera llamada al método GetSourceLabel, el primer patrón coincide con un valor de argumento porque el tipo en tiempo de ejecución int[] del argumento deriva del tipo Array. En la segunda llamada al método GetSourceLabel, el tipo en tiempo de ejecución List<T> del argumento no deriva del tipo Array, pero implementa la interfaz ICollection<T>.

  • El tipo en tiempo de ejecución del resultado de una expresión es un tipo de valor que admite valores NULL con el tipo subyacente T.

  • Existe una conversión boxing o unboxing del tipo en tiempo de ejecución del resultado de una expresión al tipo T.

En el ejemplo siguiente se muestran las dos últimas condiciones:

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

Si solo desea comprobar el tipo de una expresión, puede usar un patrón de descarte _ en lugar del nombre de una variable, como se muestra en el ejemplo siguiente:

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
    public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
    {
        Car _ => 2.00m,
        Truck _ => 7.50m,
        null => throw new ArgumentNullException(nameof(vehicle)),
        _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
    };
}

A partir de C# 9.0, para ese propósito se puede usar un patrón de tipo, como se muestra en el ejemplo siguiente:

public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
    Car => 2.00m,
    Truck => 7.50m,
    null => throw new ArgumentNullException(nameof(vehicle)),
    _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

Al igual que un patrón de declaración, un patrón de tipo coincide con una expresión cuando el resultado de una expresión no es NULL y su tipo en tiempo de ejecución cumple cualquiera de las condiciones mencionadas anteriormente.

Para obtener más información, vea las secciones Patrón de declaración y Patrón de tipo de las notas de propuesta de características.

Patrón de constante

A partir de C# 7.0, se usa un patrón de constante para probar si el resultado de una expresión es igual a una constante especificada, como se muestra en el ejemplo siguiente:

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

En un patrón de constante, se puede usar cualquier expresión constante, como:

Use un patrón de constante para comprobar null, como se muestra en el ejemplo siguiente:

if (input is null)
{
    return;
}

El compilador garantiza que no se invoca ningún operador de igualdad sobrecargado por el usuario == cuando se evalúa la expresión x is null.

A partir de C# 9.0, se puede usar un patrón de constante negado null para comprobar si no es NULL, como se muestra en el ejemplo siguiente:

if (input is not null)
{
    // ...
}

Para obtener más información, vea la sección Patrón de constante de la nota de propuesta de características.

Patrones relacionales

A partir de C# 9.0, se usa un patrón relacional para comparar el resultado de una expresión con una constante, como se muestra en el ejemplo siguiente:

Console.WriteLine(Classify(13));  // output: Too high
Console.WriteLine(Classify(double.NaN));  // output: Unknown
Console.WriteLine(Classify(2.4));  // output: Acceptable

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

En un patrón relacional, se puede usar cualquiera de los operadores relacionales <, >, <= o >=. La parte derecha de un patrón relacional debe ser una expresión constante. La expresión constante puede ser de tipo entero, de punto flotante, de carácter o de enumeración.

Para comprobar si el resultado de una expresión está en un intervalo determinado, busque coincidencias con un patrón conjuntivo and, como se muestra en el ejemplo siguiente:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // output: winter

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    >= 3 and < 6 => "spring",
    >= 6 and < 9 => "summer",
    >= 9 and < 12 => "autumn",
    12 or (>= 1 and < 3) => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};

Si el resultado de una expresión es null o no se puede convertir al tipo de una constante mediante una conversión que acepta valores NULL o unboxing, un patrón relacional no coincide con una expresión.

Para obtener más información, vea la sección Patrones relacionales de la nota de propuesta de características.

Patrones lógicos

A partir de C# 9.0, se usan los combinadores de patrones not, and y or para crear los siguientes patrones lógicos:

  • Patrón de negación not que coincide con una expresión cuando el patrón negado no coincide con ella. En el ejemplo siguiente se muestra cómo se puede negar un patrón de constante null para comprobar si una expresión no es NULL:

    if (input is not null)
    {
        // ...
    }
    
  • Patrón conjuntivo and que coincide con una expresión cuando ambos patrones coinciden con ella. En el ejemplo siguiente se muestra cómo se pueden combinar patrones relacionales para comprobar si un valor se encuentra en un intervalo determinado:

    Console.WriteLine(Classify(13));  // output: High
    Console.WriteLine(Classify(-100));  // output: Too low
    Console.WriteLine(Classify(5.7));  // output: Acceptable
    
    static string Classify(double measurement) => measurement switch
    {
        < -40.0 => "Too low",
        >= -40.0 and < 0 => "Low",
        >= 0 and < 10.0 => "Acceptable",
        >= 10.0 and < 20.0 => "High",
        >= 20.0 => "Too high",
        double.NaN => "Unknown",
    };
    
  • Patrón disyuntivo or que coincide con una expresión cuando uno de los patrones coincide con ella, como se muestra en el ejemplo siguiente:

    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));  // output: winter
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));  // output: autumn
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));  // output: spring
    
    static string GetCalendarSeason(DateTime date) => date.Month switch
    {
        3 or 4 or 5 => "spring",
        6 or 7 or 8 => "summer",
        9 or 10 or 11 => "autumn",
        12 or 1 or 2 => "winter",
        _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
    };
    

Como se muestra en el ejemplo anterior, se puede usar repetidamente los combinadores de patrones en un patrón.

El combinador de patrones and tiene mayor prioridad que or. Para especificar explícitamente la prioridad, use paréntesis, como se muestra en el ejemplo siguiente:

static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Nota

El orden en el que se comprueban los patrones es indefinido. En tiempo de ejecución, se pueden comprobar primero los patrones anidados del lado derecho de los patrones or y and.

Para obtener más información, vea la sección Combinadores de patrones de la nota de propuesta de características.

Patrón de propiedad

A partir de C# 8.0, se usa un patrón de propiedad para que coincidan las propiedades o los campos de una expresión con los patrones anidados, como se muestra en el ejemplo siguiente:

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

Un patrón de propiedad coincide con una expresión cuando el resultado de una expresión no es NULL y cada patrón anidado coincide con la propiedad o el campo correspondiente del resultado de la expresión.

También puede agregar una comprobación de tipo en tiempo de ejecución y una declaración de variable a un patrón de propiedad, como se muestra en el ejemplo siguiente:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));  // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,

    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),

    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

Un patrón de propiedad es un patrón recursivo. Es decir, se puede usar cualquier patrón como patrón anidado. Use un patrón de propiedad para hacer coincidir partes de los datos con patrones anidados, como se muestra en el ejemplo siguiente:

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

En el ejemplo anterior se usan dos características disponibles en C# 9.0 y versiones posteriores: or combinador de patrones y tipos de registro.

A partir de C# 10, puede hacer referencia a propiedades o campos anidados en un patrón de propiedad. Por ejemplo, puede refactorizar el método del ejemplo anterior en el código equivalente siguiente:

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

Para obtener más información, consulte la sección Patrón de propiedad de la nota de propuesta de características y la nota de propuesta de características Patrones de propiedades extendidos.

Patrón posicional

A partir de C# 8.0, se usa un patrón posicional para deconstruir el resultado de una expresión y hacer coincidir los valores resultantes con los patrones anidados correspondientes, como se muestra en el ejemplo siguiente:

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

En el ejemplo anterior, el tipo de una expresión contiene el método Deconstruct, que se usa para deconstruir el resultado de una expresión. También puede hacer coincidir expresiones de tipos de tupla con patrones posicionales. De este modo, puede hacer coincidir varias entradas con distintos patrones, como se muestra en el ejemplo siguiente:

static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
    => (groupSize, visitDate.DayOfWeek) switch
    {
        (<= 0, _) => throw new ArgumentException("Group size must be positive."),
        (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
        (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
        (>= 10, DayOfWeek.Monday) => 30.0m,
        (>= 5 and < 10, _) => 12.0m,
        (>= 10, _) => 15.0m,
        _ => 0.0m,
    };

En el ejemplo anterior se usan patrones relacionales y lógicos, que están disponibles en C# 9.0 y versiones posteriores.

Puede usar los nombres de elementos de tupla y parámetros Deconstruct en un patrón posicional, como se muestra en el ejemplo siguiente:

var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");  // output: Sum of [1 2 3] is 6
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

También puede extender un patrón posicional de cualquiera de las siguientes maneras:

  • Agregue una comprobación de tipo en tiempo de ejecución y una declaración de variable, como se muestra en el ejemplo siguiente:

    public record Point2D(int X, int Y);
    public record Point3D(int X, int Y, int Z);
    
    static string PrintIfAllCoordinatesArePositive(object point) => point switch
    {
        Point2D (> 0, > 0) p => p.ToString(),
        Point3D (> 0, > 0, > 0) p => p.ToString(),
        _ => string.Empty,
    };
    

    En el ejemplo anterior se usan registros posicionales que proporcionan implícitamente el método Deconstruct.

  • Use un patrón de propiedad dentro de un patrón posicional, como se muestra en el ejemplo siguiente:

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • Combine dos usos anteriores, como se muestra en el ejemplo siguiente:

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

Un patrón posicional es un patrón recursivo. Es decir, se puede usar cualquier patrón como patrón anidado.

Para obtener más información, vea la sección Patrón posicional de la nota de propuesta de características.

Patrón var

A partir de C# 7.0, se usa un patrón var para buscar coincidencias con cualquier expresión, incluida null, y se asigna el resultado a una nueva variable local, como se muestra en el ejemplo siguiente:

static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

static int[] SimulateDataFetch(int id)
{
    var rand = new Random();
    return Enumerable
               .Range(start: 0, count: 5)
               .Select(s => rand.Next(minValue: -10, maxValue: 11))
               .ToArray();
}

Un patrón var es útil cuando se necesita una variable temporal dentro de una expresión booleana para contener el resultado de los cálculos intermedios. También se puede usar un patrón var cuando necesite realizar comprobaciones adicionales en las restricciones de caso when de una expresión o instrucción switch, como se muestra en el ejemplo siguiente:

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

En el ejemplo anterior, el patrón var (x, y) es equivalente a un patrón posicional (var x, var y).

En un patrón var, el tipo de una variable declarada es el tipo en tiempo de compilación de la expresión que coincide con el patrón.

Para obtener más información, vea la sección Patrón Var de la nota de propuesta de características.

Patrón de descarte

A partir de C# 8.0, se usa un patrón de descarte _ para buscar coincidencias con cualquier expresión, incluida null, como se muestra en el ejemplo siguiente:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));  // output: 5.0
Console.WriteLine(GetDiscountInPercent(null));  // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));  // output: 0.0

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

En el ejemplo anterior, se usa un patrón de descarte para controlar null y cualquier valor entero que no tenga el miembro correspondiente de la enumeración DayOfWeek. Esto garantiza que una expresión switch en el ejemplo controle todos los valores de entrada posibles. Si no se usa un patrón de descarte en una expresión switch y ninguno de los patrones de la expresión coincide con una entrada, el tiempo de ejecución produce una excepción. El compilador genera una advertencia si una expresión switch no controla todos los valores de entrada posibles.

Un patrón de descarte no puede ser un patrón en una expresión is ni una instrucción switch. En esos casos, para buscar coincidencias con cualquier expresión, use un patrón var con un patrón de descarte: var _.

Para obtener más información, vea la sección Patrón de descarte de la nota de propuesta de características.

Patrón entre paréntesis

A partir de C# 9.0, se puede incluir un paréntesis alrededor de cualquier patrón. Normalmente, esto se hace para resaltar o cambiar la prioridad en patrones lógicos, como se muestra en el ejemplo siguiente:

if (input is not (float or double))
{
    return;
}

Especificación del lenguaje C#

Para obtener más información, consulte las siguientes notas de propuestas de características:

Vea también