Pattern Matching com C#

13 de maio de 2022
Bryan Lima

Coder and Tech Consultant

  • Twitter
  • Facebook
  • LinkedIn
Pattern Matching com C#

Pattern Matching é uma técnica para testar uma expressão para determinar se ela tem determinadas características. [Microsoft C# Pattern Matching]

Pattern Matching faz o código ser mais fácil de ler e entender. Veja os exemplos abaixo.

C#
// Sem Pattern Matching
if (video.Provider.GetType() == typeof(VimeoProvider))
{
    var videoProvider = (VimeoProvider)order;
    if (videoProvider.ContaAtiva) // Propriedade genérica que VimeoProvider herdou.
    {
        if (videoProvider.ContaPremium) // Propriedade exclusiva no tipo VimeoProvider e não disponivel em outros tipos.
        { }
    }
}

Mas com Pattern Matching fica dessa forma para fazer a mesma coisa que o código acima.

C#
// Com Pattern Matching
if (video.Provider is VimeoProvider { ContaAtiva: true } videoProvider)
{
    if (videoProvider.ContaPremium)
    { }
}

No exemplo acima são utilizados vários padrões onde operador is em video.Provider is VimeoProvider é usado para verificar se os tipos são iguais (Type Pattern), e caso seja igual então é acessado o valor da propriedade { ContaAtiva: true } usando Property Pattern para fazer a comparação lógica do valor, e por ultimo, caso tenha sucesso, é usado Declaration Pattern para criar uma variável local com o nome videoProvider do tipo VimeoProvider.

Mas calma porque vou apresentar os tipos para você entender o código acima.

Todos os padrões podem ser utilizados de três formas:

  • is operator
  • switch statement
  • switch expression

Os exemplos mostrados em cada explicação serão alternados entre as três formas para mostrar como funciona.

Em .NET, existem vários padrões, segue a lista:

Constant Pattern

Verifica se um valor é igual a um valor constante especificado. Pode ser utilizado com os tipos numéricos, char, string, boolean, null e enums.

C#
var resultado  = 4 switch
{
    1 => "Um",
    2 => "Dois",
    3 => "Trê",
    4 => "Quatro",
    5 => "Cinco",
};
// resultado: Quatro

Será lançada uma exceção caso informe um valor diferente do que foi definido nas condições do switch expression. Para resolver esse problema será utilizado o Discart Pattern.

Discard Pattern

Trate de forma genérica qualquer valor que não foi explicitamente definido na condição.

É representado pelo símbolo _ no lugar do nome de uma variável e ignora o valor atribuído a ela.

Em condições com switch o _ descarta qualquer outro valor não definido nas condições. É parecido com o default case do switch e por ser mais genérico que outras condições é definido na última expressão a ser analisada e caso seja definida em qualquer outra posição acontecerá um erro de compilação.

Quando utilizado no lugar de nomes de variáveis o valor é descartado, da mesma forma que é utilizado em descontructor de tuplas.

C#
var tupla = (1, 4, 7);
if (tupla is (1, _, 7)) // segundo valor é ignorado
{ }
C#
var resultado = 9 switch
{
    1 => "Um",
    2 => "Dois",
    3 => "Três",
    4 => "Quatro",
    5 => "Cinco",
    _ => "Outro valor"
};
// resultado: Outro valor

Var Pattern

Um valor, depois de testado e ter tido sucesso, é atribuído a uma variável local e pode ser utilizado depois de => para criar um valor de retorno.

C#
var tupla = (2, 1);
var resultado = tupla switch
{
    (_, var y) when y > 4 => $"Y: {y} é maior que 4",
    var (x, y) when x < y => $"{x} menor que {y}",
    (var x, var y) when x > y => $"{x} maior que {y}",
    _ => "Outro valor"
};

Type Pattern

Verifica se um tipo é igual ao outro.

Antes do Type Pattern era necessário verificar os tipos usando GetType() ou typeof como no exemplo abaixo.

C#
if (videoProvider.GetType() == typeof(YoutubeProvider))
{ }

A mesma verificação com Type Pattern ficaria assim.

C#
if (videoProvider is YoutubeProvider)
{ }

Exemplo com switch expression o código ficaria assim.

C#
object instancia = 3;
var resultado = instancia switch
{
    string => "É um tipo string",
    int => "É um tipo inteiro",
    null => "É nulo",
    _ => "Outro tipo"
}
// resultado: É um tipo inteiro

Declaration Pattern

Cria uma variável local com o tipo testado caso um seja igual ao outro.

Exemplo sem Declaration Pattern.

C#
if (video.GetType() == typeof(VimeoProvider))
{
    var videoProvider = video as VimeoProvider;
    if (videoProvider.ContaAtiva)
    { }
}

Exemplo com Declaration Pattern.

C#
if (video is VideoProvider videoProvider)
{
    if (videoProvider.ContaAtiva)
    { }
}

Relational Patterns

Usa os operadores relacionais (<, >, <=, >=) para verificar se o valor é maior ou menor que outro valor.

C#
object valor = "Ola Mundo";
var resultado = valor switch
{
    < 3 => "Menor que três",
    >= 7 => "Maior ou igual a sete",
    double.NaN => "NaN",
    _ => "Outro valor"
};
// resultado: NaN

Logical Patterns

Usa os operadores lógicos (not, and, or) para verificar o valor de entrada.

C#
var resultado = 300 switch
{
    < 10 and not 5=> $"Menor que dez e não pode cinco",
    >= 10 and < 20 => "Maior ou igual a dez e menor que vinte",
    30 or 40 => "Trinta ou quarenta",
    var valor => $"Outro valor: {valor}"
};
// resultado: Outro valor: 300

Property Patterns

Acessa as propriedades ou atributos de um objeto que será testado.

C#
record Dimensoes(int Peso, int Profundidade, int Largura, int Altura);
record Produto(decimal Preco, Dimensoes Dimensoes, string Marca);

var resultado = new Produto(60.3m, new(2700, 5, 8, 90), "Amazon") switch
{
    { Marca: "Apple" } => "Produto criado pela Apple",
    { Preco: < 50 } => "Produto barato, entrega em 15 dias",
    { Preco: >= 50 and < 100 } p => $"Produto {p.Marca} com entrega em 7 dias",
    { Preco: >= 100 and < 200 } p => $"Produto {p.Marca} com entrega em 2 dias",
    { Dimensoes: { Peso: > 1000 } and { Altura: >= 60 } } => "Produto pesado e alto",
    var p => $"Dimensões do produto: {p.Dimensoes.Profundidade} x {p.Dimensoes.Largura} x {p.Dimensoes.Altura}"
};
// resultado: Produto Amazon com entrega em 7 dias

Positional Pattern

Verifica se a posição dos valores de entrada são as mesmas das condições definidas.

Usa descontructor para obter as posições a serem testadas.

C#
if ((true, 2) is not (true, 6))
{
    Console.WriteLine("O valor das tuplas não são iguais");
}

O exemplo abaixo é rico por usar Positional Pattern com alguns padrões já mostrados anteriormente.

C#
var resultado = (false, 0) switch
{
    (false, 0) res => $"Primeiro valor é {res.Item1} e o segundo é {res.Item2}",
    (true, 2) => "True e Dois",
    (_, 3) => "Segundo valor é três",
    (_, > 10) => "Segundo valor é maior que Dez",
    var (x, y) => $"Fallback: {x} e {y}",
};

Parenthesized Pattern

Da ênfase ou altera a precedência lógica.

C#
object valor = 6;
if (valor is not (float or double))
{ }

Referência

PUBLICADO EM: