Padrões correspondentes: as expressões is
e switch
, e os operadores and
, or
e not
em padrões
Você usa a expressão is
, a instrução switch e a expressão switch para comparar uma expressão de entrada com qualquer número de características. O C# dá suporte a vários padrões, incluindo declaração, tipo, constante, relacional, propriedade, lista, var e descarte. Os padrões podem ser combinados usando palavras-chave da lógica booliana and
, or
e not
.
As seguintes expressões e instruções C# dão suporte à correspondência de padrões:
- Expressão
is
- instrução switch
- expressão switch
Nesses constructos, você pode corresponder uma expressão de entrada com qualquer um dos seguintes padrões:
- Padrão de declaração: para verificar o tipo de tempo de execução de uma expressão e, se uma correspondência for bem-sucedida, atribua um resultado de expressão a uma variável declarada.
- Padrão de tipo: para verificar o tipo de tempo de execução de uma expressão.
- Padrão constante: para testar se um resultado de expressão é igual a uma constante especificada.
- Padrões relacionais: para comparar um resultado de expressão com uma constante especificada.
- Padrões lógicos: para testar se uma expressão corresponde a uma combinação lógica de padrões.
- Padrão de propriedade: para testar se as propriedades ou campos de uma expressão correspondem a padrões aninhados.
- Padrão posicional: para desconstruir um resultado de expressão e testar se os valores resultantes correspondem a padrões aninhados.
var
padrão: para corresponder a qualquer expressão e atribuir seu resultado a uma variável declarada.- Descartar padrão: para corresponder a qualquer expressão.
- Padrões de lista: para testar se os elementos de sequência correspondem aos padrões aninhados correspondentes. Introduzido em C# 11.
Logic, property, positionale list são padrões recursivos. Ou seja, eles podem conter padrões aninhados.
Para obter o exemplo de como usar esses padrões para criar um algoritmo controlado por dados, consulte Tutorial: Usar a correspondência de padrões para criar algoritmos controlados por tipo e controlados por dados.
Padrões de declaração e tipo
Você usa padrões de declaração e tipo para verificar se o tipo de tempo de execução de uma expressão é compatível com um determinado tipo. Com um padrão de declaração, você também pode declarar uma nova variável local. Quando um padrão de declaração corresponde a uma expressão, essa variável recebe um resultado de expressão convertida, como mostra o exemplo a seguir:
object greeting = "Hello, World!";
if (greeting is string message)
{
Console.WriteLine(message.ToLower()); // output: hello, world!
}
Um padrão de declaração com tipo T
corresponde a uma expressão quando um resultado de expressão não é nulo e qualquer uma das seguintes condições é verdadeira:
O tipo do resultado de uma expressão em tempo de execução é
T
.O tipo do resultado de uma expressão em tempo de execução deriva do tipo
T
, implementa a interfaceT
ou existe outra conversão de referência implícita dele paraT
. O exemplo a seguir demonstra dois casos em que essa condição é verdadeira: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, };
No exemplo anterior, na primeira chamada ao método
GetSourceLabel
, o primeiro padrão corresponde a um valor de argumento porque o tipoint[]
de tempo de execução do argumento deriva do tipo Array. Na segunda chamada para o métodoGetSourceLabel
, o tipo List<T> de tempo de execução do argumento não deriva do tipo Array, mas implementa a interface ICollection<T>.O tipo do resultado de uma expressão em tempo de execução é um tipo de valor anulável com o tipo subjacente
T
.Existe uma conversão boxing ou uma conversão unboxing do tipo do resultado de uma expressão em tempo de execução para o tipo
T
.
O exemplo a seguir demonstra as duas últimas condições:
int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
Console.WriteLine(a + b); // output: 30
}
Se você quiser verificar apenas o tipo de uma expressão, poderá usar um descarte _
no lugar do nome de uma variável, como mostra o exemplo a seguir:
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 essa finalidade, você pode usar um padrão de tipo, conforme mostrado no seguinte exemplo:
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)),
};
Como um padrão de declaração, um padrão de tipo corresponde a uma expressão quando um resultado de expressão não é nulo e seu tipo de tempo de execução atende a qualquer uma das condições listadas acima.
Para verificar se há um padrão não nulo, você pode usar um padrão de constante null
negado, como mostra o exemplo a seguir:
if (input is not null)
{
// ...
}
Para obter mais informações, consulte as seções padrão declaração e padrão de tipo das notas de proposta do recurso.
Padrão de constante
Você usa um padrão constante para testar se um resultado de expressão é igual a uma constante especificada, como mostra o exemplo a seguir:
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)),
};
Em um padrão constante, você pode usar qualquer expressão constante, como:
- um literal numérico de inteiro ou ponto flutuante
- um char
- um literal de cadeia de caracteres.
- um valor booliano
true
oufalse
- um valor enum
- o nome de um campo const declarado ou local
null
A expressão deve ser um tipo que seja conversível para o tipo constante, com uma exceção: uma expressão cujo tipo é Span<char>
ou ReadOnlySpan<char>
pode ser correspondida com cadeias de caracteres constantes em C# 11 e versões posteriores.
Use um padrão constante para verificar null
, como mostra o exemplo a seguir:
if (input is null)
{
return;
}
O compilador garante que nenhum operador de igualdade sobrecarregado pelo usuário ==
seja invocado quando a expressão x is null
for avaliada.
Você pode usar um padrão de constante negado null
para verificar se há não nulo, como mostra o exemplo a seguir:
if (input is not null)
{
// ...
}
Para obter mais informações, consulte a seção Padrão constante da nota da proposta do recurso.
Padrões relacionais
Use um padrão relacional para comparar um resultado de expressão com uma constante, conforme mostrado no seguinte exemplo:
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",
};
Em um padrão relacional, você pode usar qualquer um dos operadores relacionais <
, >
, <=
ou >=
. A parte direita de um padrão relacional deve ser uma expressão constante. A expressão constante pode ser de um tipo inteiro, ponto flutuante, char ou enumerado.
Para verificar se um resultado de expressão está em um determinado intervalo, corresponda-o a um padrão conjuntivoand
, como mostra o exemplo a seguir:
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}."),
};
Se um resultado de expressão for null
ou não for convertido no tipo de uma constante por uma conversão anulável ou de unboxing, um padrão relacional não corresponderá a uma expressão.
Para obter mais informações, consulte a seção Padrões relacionais da nota da proposta do recurso.
Padrões lógicos
Use os combinadores de padrões not
, and
e or
para criar os seguintes padrões lógicos:
O padrão de negação
not
que corresponde a uma expressão quando o padrão negado não corresponder à expressão. O exemplo a seguir mostra como você pode negar um padrão constantenull
para verificar se uma expressão é não nula:if (input is not null) { // ... }
O padrão Conjuntivo
and
corresponde a uma expressão quando ambos os padrões não correspondem à expressão. O exemplo a seguir mostra como você pode combinar padrões relacionais para verificar se um valor está em um determinado intervalo: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", };
O padrão Disjuntivo
or
corresponde a uma expressão quando qualquer um dos padrões corresponde à expressão, como mostra o exemplo a seguir: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 mostra o exemplo anterior, você pode usar repetidamente os combinadores de padrão em um padrão.
Precedência e ordem da avaliação
Os combinadores de padrão são ordenados da precedência mais alta para a mais baixa da seguinte maneira:
not
and
or
Quando um padrão lógico for um padrão de uma is
expressão, a precedência de combinadores de padrões lógicos é maior do que a precedência dos operadores lógicos (operadores lógicos bit a bit e boolianos lógicos). Caso contrário, a precedência dos combinadores de padrões lógicos é menor do que a precedência de operadores lógicos e lógicos condicionais. Para obter a lista completa de operadores C# ordenados por nível de precedência, consulte a seção Precedência de operador do artigo Operadores C# .
Para especificar explicitamente a precedência, use parênteses, como mostra o exemplo a seguir:
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');
Observação
A ordem na qual os padrões são verificados é indefinida. Em tempo de execução, os padrões aninhados à direita dos padrões or
e and
podem ser verificados primeiro.
Para obter mais informações, consulte a seção Padrão constante da nota da proposta do recurso.
Padrão de propriedade
Você usa um padrão de propriedade para corresponder às propriedades ou campos de uma expressão com padrões aninhados, como mostra o exemplo a seguir:
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };
Um padrão de propriedade corresponde a uma expressão quando um resultado de expressão não é nulo e cada padrão aninhado corresponde à propriedade ou campo correspondente do resultado da expressão.
Você também pode adicionar uma verificação de tipo em tempo de execução e uma declaração de variável a um padrão de propriedade, como mostra o exemplo a seguir:
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."),
};
Um padrão de propriedade é um padrão recursivo. Ou seja, você pode usar qualquer padrão como aninhado. Use um padrão de propriedade para corresponder partes de dados com padrões aninhados, como mostra o exemplo a seguir:
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 } };
O exemplo anterior usa o or
combinador de padrões e os tipos de registro.
A partir do C# 10, você pode referenciar propriedades aninhadas ou campos dentro de um padrão de propriedade. Essa capacidade é conhecida como um padrão de propriedade estendido. Por exemplo, você pode refatorar o método do exemplo anterior para o seguinte código equivalente:
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start.Y: 0 } or { End.Y: 0 };
Para obter mais informações, consulte a seção Padrão de propriedade da nota de proposta do recurso e a nota de proposta de recurso Padrões de propriedade estendida.
Dica
Você pode usar a regra de estilo Simplificar padrão de propriedade (IDE0170) para melhorar a legibilidade do código sugerindo locais para usar padrões de propriedade estendidos.
Padrão posicional
Você usa um padrão posicional para desconstruir um resultado de expressão e corresponder aos valores resultantes com os padrões aninhados correspondentes, como mostra o exemplo a seguir:
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",
};
No exemplo anterior, o tipo de expressão contém o método Deconstrução, que é usado para desconstruir um resultado de expressão.
Importante
A ordem dos membros em um padrão posicional deve corresponder à ordem dos parâmetros no método Deconstruct
. Isso ocorre porque o código gerado para o padrão posicional chama o método Deconstruct
.
Você também pode corresponder expressões de tipos de tupla com padrões posicionais. Dessa forma, você pode corresponder várias entradas com vários padrões, como mostra o exemplo a seguir:
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,
};
O exemplo anterior usa padrões relacionais e lógicos.
Você pode usar os nomes de elementos de tupla e parâmetros Deconstruct
em um padrão posicional, como mostra o exemplo a seguir:
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);
}
Você também pode estender um padrão posicional de qualquer uma das seguintes maneiras:
Adicione uma verificação de tipo de tempo de execução e uma declaração de variável, como mostra o exemplo a seguir:
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, };
O exemplo anterior usa registros posicionais que fornecem implicitamente o método
Deconstruct
.Use um padrão de propriedade dentro de um padrão posicional, como mostra o exemplo a seguir:
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 dois usos anteriores, como mostra o exemplo a seguir:
if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p) { // .. }
Um padrão posicional é um padrão recursivo. Ou seja, você pode usar qualquer padrão como aninhado.
Para obter mais informações, consulte a seção Padrão constante da nota da proposta do recurso.
Padrão var
Você usa um var
padrão para corresponder a qualquer expressão, incluindo null
, e atribuir seu resultado a uma nova variável local, como mostra o exemplo a seguir:
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();
}
Um padrão var
é útil quando você precisa de uma variável temporária dentro de uma expressão booliana para manter o resultado de cálculos intermediários. Você também poderá usar um padrão var
quando precisar realizar mais verificações em proteções de caso when
de uma expressão ou instrução switch
, como mostra o exemplo a seguir:
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 }
}
No exemplo anterior, o padrão var (x, y)
é equivalente a um padrão posicional (var x, var y)
.
Em um padrão var
, o tipo de uma variável declarada é o tipo de tempo de compilação da expressão que corresponde ao padrão.
Para obter mais informações, consulte a seção Padrão constante da nota da proposta do recurso.
Padrão de descarte
Você usa um padrão de descarte _
para corresponder a qualquer expressão, incluindo null
, conforme mostra o exemplo a seguir:
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,
};
No exemplo anterior, um padrão de descarte é usado para tratar null
e qualquer valor inteiro que não tenha o membro correspondente da enumeração DayOfWeek. Isso garante que uma expressão switch
no exemplo manipule todos os valores de entrada possíveis. Se você não usar um padrão de descarte em uma expressão switch
e nenhum dos padrões da expressão corresponder a uma entrada, o runtime gerará uma exceção. O compilador gera um aviso se uma expressão switch
não manipular todos os valores de entrada possíveis.
Um padrão de descarte não pode ser um padrão em uma expressão is
ou uma instrução switch
. Nesses casos, para corresponder a qualquer expressão, use um var
padrão com um descarte: var _
. Um padrão de descarte não pode ser um padrão em uma switch
expressão.
Para obter mais informações, consulte a seção Padrão constante da nota da proposta do recurso.
Padrão entre parênteses
Você pode colocar parênteses em torno de qualquer padrão. Normalmente, você faz isso para enfatizar ou alterar a precedência em padrões lógicos, como mostra o exemplo a seguir:
if (input is not (float or double))
{
return;
}
Padrões de lista
A partir do C# 11, você pode fazer a correspondência de uma matriz ou de uma lista com uma sequência como mostra o exemplo a seguir:
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 mostra o exemplo anterior, um padrão de lista é correspondido quando cada padrão aninhado é correspondido pelo elemento correspondente de uma sequência de entrada. Você pode usar qualquer padrão dentro de um padrão de lista. Para corresponder a qualquer elemento, use o padrão discard ou, se você também quiser capturar o elemento, o padrão var, como mostra o exemplo a seguir:
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.
Os exemplos anteriores correspondem a uma sequência de entrada inteira em relação a um padrão de lista. Para corresponder elementos somente no início ou/e no final de uma sequência de entrada, use o padrão de fatia ..
como mostra o seguinte exemplo:
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
Um padrão de fatia corresponde a zero ou mais elementos. Você pode usar no máximo um padrão de fatia em um padrão de lista. O padrão de fatia só pode aparecer em um padrão de lista.
Você também pode aninhar um subpadrão dentro de um padrão de fatia, como mostra o exemplo a seguir:
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 obter mais informações, confira a nota de proposta de recursos de Padrões de lista.
Especificação da linguagem C#
Para obter mais informações, confira a seção Padrões e padrões correspondentes da especificação da linguagem C#.
Para informações sobre os recursos adicionados ao C# 8 e versões posteriores, confira as seguintes notas sobre a proposta de recursos:
- Correspondência de padrão recursivo
- Atualizações sobre padrões correspondentes
- C# 10 – Padrões de propriedade estendida
- C# 11 – Padrões de lista
- C# 11 – Correspondência de padrão
Span<char>
no literal de cadeia de caracteres