C# 9.0 - Melhoramentos no Pattern Matching

Neste artigo veremos os melhoramento introduzidos no recurso Pattern Matching no C# 9.0.

Dentre as novidades do C# 9.0 temos seis mudanças principais no recurso Pattern Matching ou correspondência de padrões, aprimoramento dos padrões de tipo ou Type Pattern e outros padrões.

Mas o que vem a ser essa tal correspondência de padrões ?

A correspondência de padrões é o ato de verificar se um determinado objeto atende a determinados critérios.

Esses critérios podem variar de “ser uma instância de um tipo” a “ter uma propriedade cujo valor está dentro de uma faixa de valores”.

Quando usar correspondência de padrões ?

A correspondência de padrões é excelente para atravessar estruturas de objetos complexos quando o sistema de tipos não pode nos ajudar.

Um bom exemplo é ao explorar um objeto recebido de uma API REST externa.  Além disso, o recurso pode ser usado para criar uma máquina de estado finito.

Onde usar correspondência de padrões ?

A correspondência de padrões é uma verificação, portanto, pode ser usada sempre que estivermos introduzindo um branch em nosso código.

Os cenários típicos são:

Atualmente a linguagem C# suporta muitos tipos de padrões: alguns são mais comuns do que outros e todos eles podem ser combinados para criar expressões muito poderosas.

Apenas para constar segue a lista dos principais padrões:

Vejamos a seguir algumas das novidades no C# 9.0 para o recurso Pattern Matching.

Nos exemplos a seguir vamos criar um projeto Console do tipo .NET Core usando o .NET Core 5.0.

Padrão Relacional ou Relational Patterns

No C# 9 você pode usar um padrão relacional que permite o uso de <,>, <= e> = em padrões como este:

using System;
namespace CShp_PatternMatching
{
    public class Produto
    {
        public string Nome { get; set; }
        public int CategoriaId { get; set; }
    }
    public class Livro : Produto
    {
        public string ISBN { get; set; }
    }
    public class Notebook : Produto
    {
        public bool TemBluetooth { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var produto = new Produto { Nome = "Caderno", CategoriaId = 4 };

            var resultado = GetImposto(produto); // Retorna 5
            Console.WriteLine($"Imposto = {resultado}");
            Console.ReadLine();
        }
        // Padrão Relacional
        private static int GetImposto(Produto p) => p.CategoriaId switch
        {
            1 => 0,
            < 5 => 5,
            > 20 => 15,
            _ => 10
        };
    }
}

Veja como o padrão relacional ficou mais simples de usar.

Padrão Lógico ou Logical Pattern

O C# 9 permite que você use operadores lógicos como ‘and’, ‘or’ e ‘not’, eles podem até ser combinados com padrões relacionais como mostra o exemplo a seguir:

   public class Produto
    {
        public string Nome { get; set; }
        public int CategoriaId { get; set; }
    }
    public class Livro : Produto
    {
        public string ISBN { get; set; }
    }
    public class Notebook : Produto
    {
        public bool TemBluetooth { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var produto = new Produto { Nome = "", CategoriaId = 4 };
            var resultado = GetImposto(produto); // Retorna 5
            Console.WriteLine($"Imposto = {resultado}");
            Console.ReadLine();
        }
        // Padrão Relacional combinado com padrão lógico
        private static int GetImposto(Produto p) => p.CategoriaId switch
        {
            0 or 1 => 0,
            > 1 and < 5 => 5,
            > 20 => 15,
            _ => 10
        };
    }

Padrão de Negação ou Not Pattern

No C# 9, agora o operador lógico 'not' também pode ser usado em uma instrução if e também funciona com uma instrução ternária, como mostra o código abaixo:

    public class Produto
    {
        public string Nome { get; set; }
        public int CategoriaId { get; set; }
    }
    public class Livro : Produto
    {
        public string ISBN { get; set; }
    }
    public class Notebook : Produto
    {
        public bool TemBluetooth { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var produto = new Produto { Nome = "", CategoriaId = 4 };
           
            GetDesconto(produto);               // Retorna 25
            GetDescontoTernario(produto);  // Retorna 25
            Console.ReadLine();
        }
        // padrão de negação (Not Pattern)
        private static int GetDesconto(Produto p)
        {
            if (p is not Notebook)
                return 25;
            return 0;
        }

        private static int GetDescontoTernario(Produto p) 
            => p is not Notebook ? 25 : 0;
    }

Padrão de Tipo ou Type Pattern

Outra melhoria do C# 9 com relação ao padrão de tipo foi que agora quando um tipo corresponde, o símbolo de sublinhado _ (conhecido como parâmetro de descarte) pode ser omitido, o que torna a sintaxe mais leve:

Vamos resolver o problema usando a API Lazy<T>:

    public class Produto
    {
        public string Nome { get; set; }
        public int CategoriaId { get; set; }
    }
    public class Livro : Produto
    {
        public string ISBN { get; set; }
    }
    public class Notebook : Produto
    {
        public bool TemBluetooth { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var produto = new Produto { Nome = "", CategoriaId = 4 };
            GetDescontoTipoSimples(produto);
            Console.ReadLine();
        }
        // padrão de tipo simples ou type pattern
        private static int GetDescontoTipoSimples(Produto p) => p switch
        {
            Notebook => 0, 
            Livro b => 75, 
            _ => 25
        };
    }

No código acima, antes do C# 9.0 teríamos que escrever o código usando o padrão simples assim:

// padrão de tipo simples ou type pattern
        private static int GetDescontoTipoSimples(Produto p) => p switch
        {
            Notebook _ => 0,
            Livro b _ => 75,
            _ => 25
        };

Aqui note que usamos o sublinhado (_).

E estamos conversados...

"E não comuniqueis com as obras infrutuosas das trevas, mas antes condenai-as. Porque o que eles fazem em oculto até dizê-lo é torpe. Mas todas estas coisas se manifestam, sendo condenadas pela luz, porque a luz tudo manifesta."
Efésios 5:11-13

Referências:


José Carlos Macoratti