Pattern matching no C# 8.0
Pattern matching em C#? Neste post exploramos como este recurso de programação funcional pode melhorar nossos programas em C# 8.0.

A partir do C# 7.0 a linguagem começou a receber funcionalidades que fazem uso de um recurso de programação funcional chamado pattern matching. A maioria desses recursos é derivada do F# e a introdução deles no C# visa facilitar a vida do desenvolvedor e solução de certos tipos de problemas.
São recursos muito poderosos e que simplificam em muito alguns tipos de construções.
Como os recursos de pattern matching do C# 8.0 são extensões daqueles instroduzidos no C# 7.0, vou apresentá-los na mesma ordem para simplificar o entendimento.
#Patterns introduzidos no C# 7.0
No C# 7.0 o operador is e a declaração switch foram melhoradas com a adição de alguns novos padrões: const pattern, type pattern e var pattern.
#Const pattern
Vejamos a seguinte função, que recebe um objeto o
como argumento:
Antes do C# 7.0, teríamos que escrever essa verificação usando o operador ==
, mas agora podemos fazer a mesma validação com o operador is.
Este padrão também vale para inteiros ou outros valores constantes:
#Type pattern
O type pattern pode ser um pouco mais útil. Com ele podemos extrair uma variável ao verificar o tipo, tanto com o operador is quanto com o switch.
Este padrão sempre tem sucesso quando o tipo aferido é o mesmo da variável.
Com o tipo identificado, também é possível usá-lo na mesma expressão para filtrar ainda mais o objeto:
#Var pattern
O var pattern sempre é avaliado com sucesso, pois toda variável tem um tipo.
Ele é bastante útil para obter uma variável já convertida para o seu tipo verdadeiro.
Então, se passarmos uma variável do tipo Pessoa
para a variável x
no exemplo acima, a saída será O tipo de 'x' é Pessoa
.
#Usando a declaração switch com Pattern Matching
Todos os três padrões acima são utilizáveis numa declaração do tipo switch:
Como o var pattern sempre dá match, pode até ser usado como default
.
#Recursos introduzidos no C# 8.0
Já no C# 8.0, foi introduzido um pattern matching bem mais poderoso, mais parecido com o encontrado em linguagens como F# e Scala.
Antes de ver um exemplo completo, é interessante fazermos uma rápida revisão de tuplas tal como são usadas a partir do C# 7.0.
Uma tupla pode ser declarada assim:
E uma tupla pode ser facilmente decomposta desta forma:
O primeiro valor da tupla é atribuído à primeira variável, e o segundo valor é atribuído à segunda variável.
Outro aspecto importante, é a possibilidade de utilizar o operador de descarte _
.
Neste caso, comunicamos ao compilador que não estamos interessados no segundo valor da tupla, e que ele pode ser descartado.
#Full pattern matching!
No C# 8.0, é possível escrever uma declaração switch em que cada caso é uma expressão que será avaliada em tempo de execução.
Vejamos um exemplo para selecionar uma classe CSS com base no valor de uma variável do tipo bool?
:
Neste exemplo, ao rodar este código, cada uma das expressões será avaliada, na ordem em que foram declaradas. A primeira que for verdadeira (der match) será executada.
Há dois pontos interessantes para notar: o primeiro é que neste caso a expressão switch
possui um retorno que pode ser atribuído a uma variável ou retornado. O segundo ponto é que ela está usando o const pattern que vimos mais acima.
A entrada de uma expressão swtich com pattern matching pode ser uma tupla, e cada caso pode ser decomposto. Este padrão é chamado de tuple pattern. Vamos a um exemplo mais complexo:
Neste exemplo, estamos tratando várias combinações de variáveis que resultam numa avaliação Ok()
, e em outros casos resultam num Nok()
;
Este código pode ser fácil de ler e entender, mas pode ser simplificado com a introdução de outro recurso, as guardas. Vejamos um exemplo:
Neste caso, apesar de termos dois casos aparentemente idênticos (var a, var b)
, o primeiro só será executado quando a
for maior que 10
. Caso não for, o segundo poderá ser executado quando b
for menor que 1
.
Assim, o exemplo anterior poderia ser reescrito, tomando vantagem das guardas:
Vamos a um exemplo mais prático, em que fazemos um tratamento para checar a saúde de um serviço, e avaliação toma dois argumentos: 1) o sucesso na checagem e 2) o tempo de resposta do serviço:
No cenário acima, temos um uso interessante da guarda: o serviço só será considerado saudável quando o tempo de resposta for de até 5 segundos.
Esta é uma forma bastante compacta e concisa de representar vários cenários num código expressivo e fácil de ler, e é particularmente útil para reescrever cadeias complexas ou confusas de if
s.
#Implementando máquinas de estado
Esta abordagem também é bastante útil para descrever máquinas de estado.
Por exemplo, uma máquina de estado para um portão eletrônico poderia ser algo mais ou menos assim:
#Desconstrução de tipos
Um último recurso que eu gostaria de mostrar aqui é a desconstrução de tipos, ou seja, é um structure pattern.
Para exemplificar, veja esses dois structs que representam respectivamente pontos em sistemas de coordenadas 2D e 3D:
Com o método Deconstruct
implementado, podemos usar pattern matching para determinar qual quadrante um ponto 2D se encontra:
Esse é um exemplo bem interessante porque demonstra como podemos usar pattern matching para desconstruir um tipo customizado e aplicar várias guardas para chegar numa decisão.
#Conclusão
O pattern matching é um recurso extremamente poderoso e expressivo. Ele pode ajudar a simplificar muitos códigos complexos e difíceis de ler, especialmente aqueles que envolvem muitas condições e validações.
Espero que este artigo tenha demonstrado algumas das possibilidades que o pattern matching oferece, e que você possa aplicar esses conceitos nos seus próprios projetos.