Coincidencia de patrones: las expresiones is y switch y los operadores and, or y not en patrones.

Use la is expresión, la instrucción switch y la expresión switch para que coincida con una expresión de entrada con cualquier número de características. C# admite varios patrones, como declaración, tipo, constante, relacional, propiedad, lista, var y descarte. Puede combinar patrones mediante las palabras clave lógica booleanas and, ory not.

La documentación de referencia del lenguaje C# cubre la versión más reciente publicada del lenguaje C#. También contiene documentación inicial sobre las características de las versiones preliminares públicas de la próxima versión del lenguaje.

La documentación identifica cualquier característica introducida por primera vez en las últimas tres versiones del idioma o en las versiones preliminares públicas actuales.

Sugerencia

Para buscar cuándo se introdujo por primera vez una característica en C#, consulte el artículo sobre el historial de versiones del lenguaje C#.

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: verifica el tipo de una expresión en tiempo de ejecución y, si la coincidencia es exitosa, asigna el resultado de la expresión a una variable declarada.
  • Patrón de tipo: compruebe el tipo en tiempo de ejecución de una expresión.
  • Patrón constante: pruebe que un resultado de expresión es igual a una constante especificada.
  • Patrones relacionales: compare un resultado de expresión con una constante especificada.
  • Patrones lógicos: pruebe que una expresión coincide con una combinación lógica de patrones.
  • Patrón de propiedad: pruebe que las propiedades o campos de una expresión coinciden con patrones anidados.
  • Patrón posicional: deconstruye un resultado de expresión y prueba si los valores resultantes coinciden con patrones anidados.
  • var pattern: coincide con cualquier expresión y asigna su resultado a una variable declarada.
  • Descartar patrón: coincide con cualquier expresión.
  • Patrones de lista: pruebe que una secuencia de elementos coincide con los patrones anidados correspondientes.

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

Para obtener un ejemplo de cómo usar esos patrones para crear un algoritmo controlado por datos, consulte Tutorial: Uso de la coincidencia de patrones para crear algoritmos controlados por tipos y controlados por datos.

Patrones de declaración y de tipo

Use patrones de declaración y tipo para comprobar si el tipo en tiempo de ejecución de una expresión es compatible con un tipo determinado. Mediante 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, asigna la variable al resultado de la 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!
}

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 de un resultado de expresión tiene una conversión de identidad a T.
  • El tipo T es un ref struct tipo y hay una conversión de identidad de la expresión a 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. Esta condición cubre las relaciones de herencia y las implementaciones de interfaz. 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 un valor NULL con el tipo subyacente T y Nullable<T>.HasValue es true.
  • Existe una conversión boxing o unboxing del tipo en tiempo de ejecución de un resultado de expresión al tipo T cuando la expresión no es una instancia de ref struct.

Los patrones de declaración no tienen en cuenta las conversiones definidas por el usuario ni las conversiones de intervalo implícita.

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
}

Para comprobar solo el tipo de una expresión, use un 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)),
    };
}

Para ello, use 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 comprobar si no es NULL, use unpatrón de constantenull, como se muestra en el ejemplo siguiente:

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

Para obtener más información, vea las secciones Patrón de declaración y Patrón de tipo de la especificación del lenguaje C#.

Patrón de constante

El patrón constante es una sintaxis alternativa para == cuando el operando derecho es una constante. Use un patrón constante para probar si un resultado de 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:

La expresión debe ser un tipo que se puede convertir en el tipo constante, con una excepción: una expresión cuyo tipo es Span<char> o ReadOnlySpan<char> puede coincidir con cadenas constantes.

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 invoca un operador == de igualdad sobrecargado por el usuario cuando evalúa la expresión x is null.

Puede usar un patrón de constantes negadasnull para comprobar si no son 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

Use un patrón relacional para comparar un resultado de 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, use 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 un resultado de 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 la expresión.

Para obtener más información, consulte la sección Patrones relacionales de la especificación del lenguaje C#.

Patrones lógicos

Use los notcombinadores de patrones , andy or para crear los siguientes patrones lógicos:

  • Patrón de negaciónnot 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 constantenull para comprobar si una expresión no es null:

    if (input is not null)
    {
        // ...
    }
    
  • Patrón conjuntivoand 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 disyuntivoor 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, puede usar los combinadores de patrones repetidamente en un patrón.

Precedencia y orden de comprobación

Los combinadores de patrones comprueban expresiones en este orden, en función del orden de enlace de las expresiones:

  • not
  • and
  • or

El patrón not se enlaza primero a su operando. El patrón and se enlaza después de cualquier enlace de expresión de patrón not.. El or patrón se enlaza después de que todos los not patrones y and se enlazan a operandos. En el ejemplo siguiente se intenta hacer coincidir todos los caracteres que no son letras minúsculas, a a través zde . Tiene un error, porque el patrón not se enlaza antes del patrón and:

// Incorrect pattern. `not` binds before `and`
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';

El enlace predeterminado significa que el ejemplo anterior se analiza como el ejemplo siguiente:

// The default binding without parentheses is shows in this method. `not` binds before `and`
static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');

Para corregir el error, especifique que desea que el not patrón se enlace a la >= 'a' and <= 'z' expresión:

// Correct pattern. Force `and` before `not`
static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');

Agregar paréntesis se vuelve más importante a medida que los patrones se vuelven más complicados. En general, use paréntesis para aclarar los patrones de otros desarrolladores, 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 el compilador comprueba los patrones que tienen el mismo orden de enlace no está definido. En tiempo de ejecución, el compilador puede comprobar primero los patrones anidados a la derecha de varios or patrones y varios and patrones.

Para obtener más información, consulte la sección Combinación de patrones de la especificación del lenguaje C#.

Patrón de propiedad

Use un patrón de propiedad para hacer coincidir las propiedades o campos de una expresión con 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 la expresión no es NULL y cada patrón anidado coincide con la propiedad o campo correspondientes del resultado de la expresió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."),
};

Esta construcción significa específicamente que el patrón de propiedad is { } coincide con todo lo que no es NULL y puede usarlo en lugar de is not null para crear una variable: somethingPossiblyNull is { } somethingDefinitelyNotNull.

if (GetSomeNullableStringValue() is { } nonNullValue) // Empty property pattern with variable creation
{
    Console.WriteLine("NotNull:" + nonNullValue);
}
else
{
    nonNullValue = "NullFallback"; // we can access the variable here.
    Console.WriteLine("it was null, here's the fallback: " + nonNullValue);
}

Un patrón de propiedad es un patrón recursivo. 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 usa el orcombinador de patrones y los tipos de registro.

Puede hacer referencia a propiedades o campos anidados dentro de un patrón de propiedades. Esta funcionalidad se conoce como patrón de propiedad extendida. 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 propiedades del estándar de C#.

Sugerencia

Para mejorar la legibilidad del código, use la regla de estilo Simplificar patrón de propiedades (IDE0170). Sugiere lugares para usar patrones de propiedad extendidos.

Patrón posicional

Use un patrón posicional para deconstruir 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 el patrón usa para deconstruir un resultado de expresión.

Importante

El orden de los miembros de un patrón posicional debe coincidir con el orden de los parámetros del método Deconstruct. El código generado para el patrón posicional llama al Deconstruct método .

También puede hacer coincidir expresiones de tipos de tupla con patrones posicionales. Con este enfoque, puede hacer coincidir varias entradas con varios 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.

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 los 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.

var patrón

Use un var patrón para que coincida con cualquier expresión, incluido null, y asigne su 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 mayúsculas y minúsculas 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 var patrón, 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

Use un patrón_ de descarte para que coincida con cualquier expresión, incluido 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, un patrón de descarte controla null y cualquier valor entero que no tenga el miembro correspondiente de la DayOfWeek enumeración. Esa garantía garantiza que una switch expresión del ejemplo controla 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 _. Un patrón de descarte puede ser un patrón en una expresión switch.

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

Puede colocar paréntesis alrededor de cualquier patrón. Normalmente, lo hace para resaltar o cambiar la prioridad en los patrones lógicos, como se muestra en el ejemplo siguiente:

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

Patrones de lista

Puede hacer coincidir una matriz o una lista con una secuencia de patrones, como se muestra en el ejemplo siguiente:

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]);  // True

Como se muestra en el ejemplo anterior, un patrón de lista coincide cuando cada patrón anidado coincide con el elemento correspondiente de una secuencia de entrada. Puede usar cualquier patrón dentro de un patrón de lista. Para que coincida con cualquier elemento, use el patrón de descarte o, si también desea capturar el elemento, use el patrón var, como se muestra en el ejemplo siguiente:

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [var first, _, _])
{
    Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.

Los ejemplos anteriores buscan coincidencias de una secuencia de entrada completa con un patrón de lista. Para hacer coincidir los elementos solo al principio o al final ( o ambos) de una secuencia de entrada, use el patrón .., como se muestra en el ejemplo siguiente:

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

Un patrón de segmento busca coincidencias con cero o más elementos. Puede usar como máximo un patrón de segmento en un patrón de lista. El patrón de sector solo puede aparecer en un patrón de lista.

También puede anidar un subpatrón dentro de un patrón de segmento, como se muestra en el ejemplo siguiente:

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron");  // output: Message apron doesn't match.

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });  // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

Para obtener más información, vea Patrón de lista en la especificación del lenguaje C#.

Patrones de jerarquía cerrados

A partir de C# 15, una switch expresión cuyo tipo de gobierno es una closed clase es exhaustiva cuando sus brazos controlan cada descendiente directo de esa clase. El compilador no requiere un brazo predeterminado porque el modificador es exhaustivo:

public closed record class PaymentMethod;
public record class Cash : PaymentMethod;
public record class Card(string Last4) : PaymentMethod;
public record class BankTransfer(string Iban) : PaymentMethod;
public static string Describe(PaymentMethod method) => method switch
{
    Cash => "cash",
    Card(var last4) => $"card ending {last4}",
    BankTransfer(var iban) => $"bank transfer to {iban}",
    // No warning: every direct descendant of 'PaymentMethod' is handled.
};

Un modificador de jerarquía cerrada solo es exhaustivo cuando se puede acceder a cada descendiente directo desde la ubicación del conmutador. Si un descendiente directo es menos accesible que el tipo base cerrado y no está visible en el sitio del conmutador, el compilador lo trata como no controlado y advierte de que el modificador no es exhaustivo.

Por ejemplo, una clase base cerrada public puede tener un internal descendiente directo. El código del mismo ensamblado ve el conjunto completo de descendientes, pero el código de otro ensamblado no:

// Assembly 1
public closed record class Shape;
public record class Circle(double Radius) : Shape;
internal record class Triangle(double Base, double Height) : Shape;
// Same assembly: 'Triangle' is visible, so the switch is exhaustive.
internal static double Area(Shape shape) => shape switch
{
    Circle(var r) => Math.PI * r * r,
    Triangle(var b, var h) => 0.5 * b * h,
};
// Assembly 2
public static string Name(Shape shape) => shape switch
{
    Circle => "circle",
    // Warning CS8509: the switch expression doesn't handle all possible values.
    // 'Triangle' is a direct descendant of 'Shape' but isn't visible here.
};

Para restaurar la exhaustiva en el ensamblado 2, agregue un brazo de descarte (_ => ...), agregue un brazo de clase base (Shape en el ejemplo anterior) o haga que todos los descendientes directos sean al menos tan accesibles como el tipo base cerrado.

Cuando el tipo de gobierno admite valores NULL, null es un valor adicional que el modificador debe controlar. Un modificador PaymentMethod? que omite un null brazo no es exhaustivo incluso cuando se coincide con cada descendiente directo.

La derivación de una clase cerrada no es transitiva: un descendiente no cerrado de una clase cerrada se puede derivar de en otros ensamblados. El compilador solo trata los descendientes directos como conjunto exhaustivo. Para hacer que un modificador sobre un descendiente también se beneficie de la comprobación de la exhaustiva, declare el descendiente closed (o sealed).

Dado que los descendientes indirectos no expanden el conjunto exhaustivo de la base cerrada, no es necesario agregar un brazo para cada subtipo transitivo para satisfacer la exhaustivaidad. La subsumpción entre brazos sigue funcionando de la misma manera que para cualquier jerarquía de clases: un brazo que coincide con un tipo base cubre cada subtipo y un brazo posterior que coincide con uno de esos subtipos es inaccesible. Considere un elemento cerrado Vehicle cuyos descendientes directos son Car y Truck, además de un descendiente Sedan indirecto declarado en otro ensamblado:

// Assembly 1
public closed record class Vehicle;
public record class Car(int Doors) : Vehicle;
public record class Truck(double PayloadTons) : Vehicle;

// Assembly 2
public record class Sedan(int Doors) : Car(Doors);

Una conmutación Vehicle es exhaustiva después de controlar Car y Truck, aunque Sedan existe. El Car brazo cubre todos los Sedan valores:

public static string Category(Vehicle v) => v switch
{
    Car => "car",
    Truck => "truck",
    // No warning. The 'Car' arm covers 'Sedan' through ordinary subtype matching.
};

Para enviar en Sedan concreto, coloque su brazo delante del Car brazo. El Car brazo sigue siendo accesible porque sigue coincidendo con todos los Car elementos que no son :Sedan

public static string CategorySedanFirst(Vehicle v) => v switch
{
    Sedan => "sedan",
    Car => "car",      // Reachable: 'Car' values that aren't 'Sedan'.
    Truck => "truck",
};

Al revertir esos dos brazos, se produce un error de subsumpción, como lo haría en cualquier otra jerarquía de clases. El compilador detecta que el Car brazo ya cubre Sedan:

string Category(Vehicle v) => v switch
{
    Car => "car",
    Sedan => "sedan",  // Error CS8510: the pattern is unreachable. It has already been handled by 'Car => ...'.
    Truck => "truck",
};

Si desea que la exhaustiva siga la jerarquía más abajo, declare Car a sí misma closed. A continuación, el compilador trata todos los descendientes directos de Car (como Sedan) como el conjunto exhaustivo raíz en Car. Un modificador cuyo tipo de gobernanza es Car exhaustivo cuando realiza alguna de las siguientes acciones:

  • Controla cada descendiente directo de Car con su propio brazo.
  • Incluye un Car brazo, que cubre todos Car los valores (incluidos todos los subtipos).
  • Incluye un brazo de descarte (_ => ...) o un brazo para un tipo base de Car, como Vehicle.

Un modificador cuyo tipo de gobierno es Vehicle tiene opciones: código que controla Car y Truck sigue siendo exhaustivo, ya que el Car brazo cubre todos los subtipos de Car. Marcar Carclosed simplemente le ofrece una segunda opción para ese modificador. Puede mantener el brazo único Car o reemplazarlo por un brazo por descendiente directo de Car (junto con el Truck brazo) y seguir siendo exhaustivo.

El marcado Carclosed también lo hace implícitamente abstract, lo que significa que ya no se pueden crear instancias de Car directamente. Es posible que no se ajuste a su diseño. Si necesita Car permanecer al instante, déjelo abierto y envíe en los subtipos específicos que le interesa al ordenar los brazos como se mostró anteriormente.

Tipos de parámetro de tipo que rigen los tipos

Una switch expresión cuyo tipo de gobierno es un parámetro de tipo restringido a una clase cerrada es exhaustivo en los mismos términos que un modificador sobre la propia clase cerrada. Para enviar en una jerarquía cerrada desde código genérico, restrinja el parámetro type a la base cerrada y controle todos los descendientes directos:

public static string Describe<X>(X method) where X : PaymentMethod => method switch
{
    Cash => "cash",
    Card(var last4) => $"card ending {last4}",
    BankTransfer(var iban) => $"bank transfer to {iban}",
    // No warning: 'X' is constrained to the closed type 'PaymentMethod',
    // so handling every direct descendant exhausts the switch.
};

Para obtener más información, vea el modificador cerrado. Para obtener la especificación, consulte Jerarquías cerradas.

Patrones de unión

A partir de C# 15, cuando el valor entrante de un patrón es un tipo de unión, los patrones desencapsulan automáticamente la unión. Se aplican a la propiedad de la unión en lugar del Value propio valor de unión. Este comportamiento hace que la unión sea transparente para la coincidencia de patrones:

public record class Cat(string Name);
public record class Dog(string Name);
public union Pet(Cat, Dog);

string Describe(Pet pet) => pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
};

Dos patrones son excepciones: el var patrón y el patrón de descarte _ se aplican al propio valor de unión, no a su Value propiedad.

El null patrón comprueba si el valor de la Value unión es NULL. En el caso de las uniones basadas en clases, null también se realiza correctamente cuando la propia referencia de unión es null.

Cuando un tipo de unión proporciona el patrón de acceso no boxing (HasValue y TryGetValue miembros), el compilador usa esos miembros para evitar casos de tipo de valor boxing durante la coincidencia de patrones.

Para obtener más información, consulte Coincidencia de uniones. Para obtener la especificación, consulte Uniones.

Especificación del lenguaje C#

Para obtener más información, consulte la sección Patrones y coincidencia de patrones de la especificación del lenguaje C#, entre las que se incluyen:

Consulte también